Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/building/verification/how-grading-works.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,8 @@ Runner summaries distinguish **failure** from **coverage**:
| `requirement_unmet` skip naming `controller` on controller-seeded storyboards | The seller exposes no [`comply_test_controller`](/docs/building/by-layer/L3/comply-test-controller#who-is-expected-to-implement-the-controller); reference sellers SHOULD implement it, translator and adapter sellers that cannot seed state are not expected to | Reference sellers: implement the controller. Translator and adapter sellers: no ask; publish the skipped coverage list |
| A `missing_tool` skip for a declared protocol or specialism, including in a scenario the specialism requires (for example, a `sales-*` seller that does not expose [`sync_governance`](/docs/accounts/tasks/sync_governance)) | The seller declared a claim the runner could not fully test. [Strict Spec grading](/docs/building/verification/aao-verified#grading-profiles) counts the specialism as incomplete (`partial`). Legacy grading treats a storyboard skipped because the agent advertises none of its `required_tools` as not applicable, so that gap does not block a Legacy badge | Fix the missing surface or narrow the declaration |
| A universal storyboard listed in `storyboards_missing_tools` because the agent advertises none of its `required_tools`, where none of those tools belongs to a protocol the agent declares (see [Required tasks by protocol](/docs/protocol/required-tasks)) and no capability the agent declares requires them (an `account` block, `adcp.principal.supported: true`, or capability-change notifications) | The agent does not offer that surface. Neither grading profile counts it: Strict Spec grading reports the universal bundle as not applicable rather than incomplete. For example, a signals-only agent is not graded on storyboards that need [`get_products`](/docs/media-buy/task-reference/get_products), [`list_creatives`](/docs/creative/task-reference/list_creatives), [`sync_accounts`](/docs/accounts/tasks/sync_accounts) or [`sync_principal`](/docs/protocol/sync_principal). Discovery and `comply_test_controller` are never treated this way | No implementation ask |
| Any `unsatisfied_contract` or other `requirement_unmet` skip (except the `controller` row above) for a declared protocol or specialism | The seller declared a claim the runner could not fully test | Fix the missing surface or narrow the declaration |
| The [`signed_requests`](/compliance/latest/universal/signed-requests.yaml) storyboard skipped with an unmet `request_signer` requirement because the agent does not advertise `request_signing.supported: true` | The agent does not offer verified signed requests, which the storyboard says is not a failure. Strict Spec grading reports the universal bundle as not applicable rather than incomplete. Other unmet runner requirements, such as no webhook receiver or a missing test-kit contract, still count as incomplete | No implementation ask |
| Any `unsatisfied_contract` or other `requirement_unmet` skip (except the `controller` and `request_signer` rows above) for a declared protocol or specialism | The seller declared a claim the runner could not fully test | Fix the missing surface or narrow the declaration |
| A declared specialism the graded compliance release does not define, because AdCP introduced it later | Reported `not_applicable` for that specialism; the rest of the run is graded normally, and the specialism neither earns nor blocks a badge for this release | No implementation ask — advertise a release that defines the specialism in `adcp.supported_versions` to have it graded and badged |

This distinction matters for production-path sandbox runs. A seller can run the storyboard suite against the real production endpoint under sandbox-flagged traffic and get **zero failures** while still seeing a `partial` summary because the production endpoint correctly does not expose [`comply_test_controller`](/docs/building/by-layer/L3/comply-test-controller). After confirming that the raw storyboard results contain no failed storyboard-scoped assertions, that result says: "the buyer-visible sandbox path passed every assertion the runner could grade, but controller-seeded lifecycle scenarios were skipped." It is useful evidence for sandbox readiness, but it is not the same as full deterministic specialism coverage.
Expand Down
5 changes: 4 additions & 1 deletion server/src/schemas/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -908,8 +908,11 @@ const BadgeGradingEvidenceSchema = z.object({
relevant_bundle_ids: z.array(z.string()),
failing_bundle_count: z.number().int().nonnegative(),
incomplete_bundle_count: z.number().int().nonnegative(),
incomplete_bundle_ids: z.array(z.string()).optional().openapi({
description: "Relevant bundles, as kind:id, that keep Strict Spec partial because their evidence is incomplete: an untested or partially covered storyboard, including runner and test-kit gaps.",
}),
not_applicable_universal_bundle_ids: z.array(z.string()).optional().openapi({
description: "Universal bundles left partial only because the agent advertises none of the tools for a surface it does not declare. They do not count as incomplete.",
description: "Universal bundles left partial only because the agent does not offer the surface: it advertises none of the tools for a surface it does not declare, or it does not advertise request signing for signed_requests. They do not count as incomplete.",
}),
relevant_failure_count: z.number().int().nonnegative(),
missing_universal_bundle: z.boolean(),
Expand Down
21 changes: 14 additions & 7 deletions server/src/services/verification-profile-assessment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
hostedComplianceTarget,
} from './hosted-compliance-version.js';
import { undeclaredSurfaceMissingToolStoryboards } from './verification-profile-tool-obligations.js';
import { requestSigningNotAdvertised, SIGNED_REQUESTS_STORYBOARD } from './verification-profile-request-signing.js';

export const VERIFICATION_PROFILE_ROLE_POLICY_VERSION = 'verification-profiles-role-v1';

Expand Down Expand Up @@ -68,9 +69,10 @@ function legacyStatus(role: ReturnType<typeof deriveVerificationStatus>['roles']
* additionally requires complete universal, role-protocol, and every declared
* stable-specialism bundle. A universal bundle the runner left partial only
* because the agent advertises none of the tools for a surface it never
* declared is not applicable, not incomplete. Unattributed failures fail every
* role closed. Sandbox remains visible but unavailable until causal bundle
* evidence ships.
* declared, or signed_requests set aside because the agent does not advertise
* request signing, is not applicable, not incomplete. Unattributed failures
* fail every role closed. Sandbox remains visible but unavailable until causal
* bundle evidence ships.
*/
export function deriveVerificationProfileRoleAssessments(input: {
result: ComplianceResult;
Expand Down Expand Up @@ -98,13 +100,17 @@ export function deriveVerificationProfileRoleAssessments(input: {
input.result,
verification.roles.map(role => role.role.replace(/-/g, '_')),
);
const setAside = new Set([
...undeclaredSurfaces,
...(requestSigningNotAdvertised(input.result) ? [SIGNED_REQUESTS_STORYBOARD] : []),
]);
const notApplicableStoryboards = new Set(input.result.storyboards_not_applicable ?? []);
const undeclaredSurfaceBundle = (bundle: (typeof bundles)[number]): boolean => {
const notApplicableUniversalBundle = (bundle: (typeof bundles)[number]): boolean => {
const storyboardIds = bundle.storyboard_ids ?? [];
return bundle.kind === 'universal'
&& bundle.status === 'partial'
&& storyboardIds.some(id => undeclaredSurfaces.has(id))
&& storyboardIds.every(id => undeclaredSurfaces.has(id) || notApplicableStoryboards.has(id));
&& storyboardIds.some(id => setAside.has(id))
&& storyboardIds.every(id => setAside.has(id) || notApplicableStoryboards.has(id));
};
const output: VerificationProfileRoleAssessmentInput[] = [];

Expand All @@ -128,7 +134,7 @@ export function deriveVerificationProfileRoleAssessments(input: {
);
const missingUniversalBundle = !relevantBundles.some(bundle => bundle.kind === 'universal');
const failingBundles = relevantBundles.filter(bundle => bundle.status === 'failing');
const notApplicableUniversalBundles = relevantBundles.filter(undeclaredSurfaceBundle);
const notApplicableUniversalBundles = relevantBundles.filter(notApplicableUniversalBundle);
const incompleteBundles = relevantBundles.filter(bundle =>
(bundle.status === 'partial' || bundle.status === 'untested')
&& !notApplicableUniversalBundles.includes(bundle),
Expand Down Expand Up @@ -162,6 +168,7 @@ export function deriveVerificationProfileRoleAssessments(input: {
relevant_bundle_ids: relevantBundles.map(bundle => `${bundle.kind}:${bundle.id}`),
failing_bundle_count: failingBundles.length,
incomplete_bundle_count: incompleteBundles.length,
incomplete_bundle_ids: incompleteBundles.map(bundle => `${bundle.kind}:${bundle.id}`),
not_applicable_universal_bundle_ids: notApplicableUniversalBundles.map(bundle => bundle.id),
relevant_failure_count: relevantFailures.length,
missing_universal_bundle: missingUniversalBundle,
Expand Down
68 changes: 68 additions & 0 deletions server/src/services/verification-profile-request-signing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import type { ComplianceResult, TestStepResult } from '@adcp/sdk/testing';
import { record } from './verification-profile-tool-obligations.js';

/**
* signed_requests "runs for any agent advertising `request_signing.supported:
* true`"; for any other agent "absence of advertisement is not a failure"
* (signed-requests.yaml). The runner then reports its `request_signer`
* requirement unmet, which the SDK grades partial on every release. Every
* other unmet requirement, test-kit gap or fixture gap stays a coverage gap
* (runner-output-contract.yaml > skip_result).
*/
export const SIGNED_REQUESTS_STORYBOARD = 'signed_requests';

/** Synthetic phase and step the runner emits for an unmet storyboard requirement. */
const REQUIREMENT_GATE_PHASE = 'requirement_unmet';
const REQUEST_SIGNER_REQUIREMENT = 'request_signer';
const REQUEST_SIGNER_GATE_STEP = `${REQUIREMENT_GATE_PHASE}:${REQUEST_SIGNER_REQUIREMENT}`;
const NOT_APPLICABLE_SKIP = 'not_applicable';

const STORYBOARD_ASSERTION_FAILED = 'storyboard-assertion-failed';

function projectedPhases(result: ComplianceResult, storyboardId: string) {
const prefix = `${storyboardId}/`;
return (result.tracks ?? [])
.flatMap(track => track.scenarios ?? [])
.filter(({ scenario }) => typeof scenario === 'string'
&& scenario.startsWith(prefix)
&& !scenario.slice(prefix.length).includes('/'));
}

function hasFailureEvidence(result: ComplianceResult, storyboardId: string): boolean {
if ((result.failures ?? []).some(failure => failure.storyboard_id === storyboardId)) return true;
return (result.observations ?? []).some(({ source }) =>
source?.code === STORYBOARD_ASSERTION_FAILED
&& 'storyboard_id' in source
&& source.storyboard_id === storyboardId);
}

/** Whether the storyboard's only result is the runner's unmet-signer row. */
function onlyUnmetSignerRow(result: ComplianceResult, storyboardId: string): boolean {
const phases = projectedPhases(result, storyboardId);
if (phases.length !== 1) return false;
const [phase] = phases;
if (phase.scenario !== `${storyboardId}/${REQUIREMENT_GATE_PHASE}` || !phase.overall_passed) return false;
if (phase.steps?.length !== 1) return false;
const [step]: TestStepResult[] = phase.steps;
return step.skipped === true
&& step.passed === true
&& (step.validations?.length ?? 0) === 0
&& step.step_id === REQUEST_SIGNER_GATE_STEP
&& step.skip_reason === NOT_APPLICABLE_SKIP
&& step.requirement === REQUEST_SIGNER_REQUIREMENT;
}

/**
* Whether the runner executed signed_requests and set it aside only because
* the agent does not advertise request signing. False whenever the
* declarations or the projected result cannot prove it, so callers fail
* closed.
*/
export function requestSigningNotAdvertised(result: ComplianceResult): boolean {
const storyboardId = SIGNED_REQUESTS_STORYBOARD;
const capabilities = record(result.agent_profile?.raw_capabilities);
if (!capabilities || record(capabilities.request_signing)?.supported === true) return false;
if (!(result.storyboards_executed ?? []).includes(storyboardId)) return false;
if ((result.storyboards_missing_tools ?? []).includes(storyboardId)) return false;
return !hasFailureEvidence(result, storyboardId) && onlyUnmetSignerRow(result, storyboardId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export function isClassifiedTask(tool: string): boolean {
return KNOWN_TASKS.has(tool);
}

function record(value: unknown): Record<string, unknown> | undefined {
export function record(value: unknown): Record<string, unknown> | undefined {
return value !== null && typeof value === 'object' && !Array.isArray(value)
? value as Record<string, unknown>
: undefined;
Expand Down
Loading
Loading