feat(compliance): pagination total_count honesty assertions#3100
Merged
Conversation
Extends `pagination_integrity` with three runtime assertions per page: - `query_summary.total_matching = 3` (always — schema requires the field) - `query_summary.returned` matches the slice (2 on continuation, 1 on terminal) - `pagination.total_count = 3` when volunteered (field_value_or_absent preserves the schema's optional stance) Catches the dishonest pagination class where an agent honors max_results and the cursor handshake but under-reports total_count to hide inventory the same way a dishonest has_more: false would. Verified by flipping the training agent's total_count to page-local count — page 1 assertion fires with the expected diagnostic. Closes the deferred follow-up flagged by the protocol reviewer on #3095. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced Apr 25, 2026
bokelley
added a commit
that referenced
this pull request
Apr 25, 2026
Adds get_signals_pagination_integrity universal storyboard mirroring the cursor↔has_more invariant that gates list_creatives (#3095/#3100) onto the signals marketplace. Page 1 asserts has_more=true with a cursor under a broad query; page 2 follows the cursor and asserts schema conformance. Fixes the training agent's handleGetSignals to honor pagination.max_results / pagination.cursor (was: capped at MAX_SIGNAL_RESULTS=10 internally and emitted no pagination block — exactly the dishonest shape this storyboard catches). Reads pagination.max_results in preference to legacy top-level max_results for forward compatibility. Generalizes the offset cursor codec from #3095 into a kind-prefixed pair so cursors can't be replayed across endpoints. encodeCreativeCursor / decodeCreativeCursor preserved as wrappers; list_creatives behavior unchanged. Negative-test verified: flipping the agent back to has_more: false fires the page-1 assertion with the expected diagnostic. Closes the second target in the rolling pagination conformance series. list_creative_formats deferred to a separate issue (#3108) pending a seed_creative_format scenario; list_accounts deferred to #3106 pending the missing handler. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley
added a commit
that referenced
this pull request
Apr 25, 2026
* feat(compliance): get_signals pagination cursor integrity Adds get_signals_pagination_integrity universal storyboard mirroring the cursor↔has_more invariant that gates list_creatives (#3095/#3100) onto the signals marketplace. Page 1 asserts has_more=true with a cursor under a broad query; page 2 follows the cursor and asserts schema conformance. Fixes the training agent's handleGetSignals to honor pagination.max_results / pagination.cursor (was: capped at MAX_SIGNAL_RESULTS=10 internally and emitted no pagination block — exactly the dishonest shape this storyboard catches). Reads pagination.max_results in preference to legacy top-level max_results for forward compatibility. Generalizes the offset cursor codec from #3095 into a kind-prefixed pair so cursors can't be replayed across endpoints. encodeCreativeCursor / decodeCreativeCursor preserved as wrappers; list_creatives behavior unchanged. Negative-test verified: flipping the agent back to has_more: false fires the page-1 assertion with the expected diagnostic. Closes the second target in the rolling pagination conformance series. list_creative_formats deferred to a separate issue (#3108) pending a seed_creative_format scenario; list_accounts deferred to #3106 pending the missing handler. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(training-agent): preserve legacy 50-cap on top-level get_signals max_results Code-reviewer flagged on #3109: the cap on top-level `max_results` shifted from 50 (prior behavior) to 100 (new code's uniform cap) when I unified the read path. No spec basis for either cap on the top-level field, but silently widening it is the wrong direction for a behavioral preserve. Restructure: pagination.max_results gets the schema's documented 100 cap, top-level max_results keeps the historical 50 cap. The two forms diverge intentionally — pagination is the standard envelope, top-level is the predecessor. Spec ambiguity on which form wins when both are present is tracked at #3113. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(conformance): add get_signals_pagination_integrity to universal-storyboards tables --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced Apr 25, 2026
bokelley
added a commit
that referenced
this pull request
Apr 25, 2026
…, list_collection_lists, list_property_lists (#3147) * feat(training-agent): add cursor pagination to governance list handlers (#3112) Adds cursor-based pagination to list_content_standards, list_collection_lists, and list_property_lists — the fifth batch in the rolling pagination conformance series (#3095, #3100, #3109, #3110). https://claude.ai/code/session_018AkoeaWmnrsnbXBtK8FLD2 * fix(compliance): scope identity + idempotency_key + valid channel on governance pagination storyboards Triage's #3147 hit two build-time lints CI surfaced: 1. **Storyboard scoping** — 15 sample_request blocks (3 storyboards × 5 steps each, minus capability_discovery) omitted brand/account identity. The create_* and list_* tasks for governance lists are tenant-scoped per `lint-storyboard-scoping`. Adds the canonical `account: { brand: { domain: 'acmeoutdoor.example' }, operator: 'pinnacle-agency.example' }` to all 15. 2. **Idempotency_key on mutating setup steps** — 9 create_* sample_requests (3 per storyboard) on `create_collection_list`/`create_content_standards`/ `create_property_list` omitted `idempotency_key`, which their request schemas mark as required. Adds `$generate:uuid_v4#<storyboard>_setup_<step>` per the established convention. 3. **Invalid channel value** — `create_standards_2` used `channels_any: ["video"]` which isn't in the channels enum. Replace with `["olv"]` (the standardized value for online video advertising outside CTV per `static/schemas/source/enums/channels.json`). Verified: 8/8 pagination storyboards pass against the training agent (list_creatives, get_signals, list_creative_formats, list_accounts, get_media_buys, content_standards, collection_lists, property_lists) — 41/41 steps clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Extends
pagination_integritywith three runtime assertions per page:query_summary.total_matching = 3(always — schema requires the field)query_summary.returnedmatches the slice (2 on continuation, 1 on terminal)pagination.total_count = 3when volunteered (field_value_or_absentpreserves the schema's optional stance)Why
Closes the deferred follow-up from #3095. The protocol reviewer flagged that the cursor↔has_more storyboard tested wire-level honesty but didn't exercise count-level honesty. An agent can honor
max_resultsand the cursor handshake while still lying in the summary — under-reportingtotal_countto hide inventory the same way a dishonesthas_more: falsewould, or driftingtotal_matchingacross pages.These assertions are pure internal-consistency checks: they don't require upstream visibility (the storyboard knows the seeded set is 3) so they catch the dishonesty without conflating wire honesty with platform honesty.
total_countstays optional viafield_value_or_absent allowed_values: [3]— agents whose backends can't compute it cheaply remain conformant; agents that volunteer it must tell the truth.Verified
Spot-checked the assertion fires correctly by flipping the training agent's
total_countfromtotalMatching(the truthful 3) topageCreatives.length(the dishonest page-local 2). Page-1 assertion fired with diagnostic:Reverted the agent — clean run again.
Test plan
npm run build:compliance— pagination-invariant lint and 7 storyboard lints cleannpm run test:pagination-invariant— 16/16 tests passnpm run typecheck— cleanpagination_integrityagainst training agent: 6/6 steps cleantotal_count🤖 Generated with Claude Code