@adcp/sdk 8.1.0-beta.5 → 8.1.0-beta.7
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/dist/lib/conformance/oracle.d.ts.map +1 -1
- package/dist/lib/conformance/oracle.js +8 -1
- package/dist/lib/conformance/oracle.js.map +1 -1
- package/dist/lib/conformance/schemaArbitrary.js +135 -9
- package/dist/lib/conformance/schemaArbitrary.js.map +1 -1
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/server/create-adcp-server.d.ts +5 -0
- package/dist/lib/server/create-adcp-server.d.ts.map +1 -1
- package/dist/lib/server/create-adcp-server.js +41 -3
- package/dist/lib/server/create-adcp-server.js.map +1 -1
- package/dist/lib/server/decisioning/capabilities.d.ts +8 -0
- package/dist/lib/server/decisioning/capabilities.d.ts.map +1 -1
- package/dist/lib/server/decisioning/proposal/dispatch.d.ts.map +1 -1
- package/dist/lib/server/decisioning/proposal/dispatch.js +2 -0
- package/dist/lib/server/decisioning/proposal/dispatch.js.map +1 -1
- package/dist/lib/server/decisioning/runtime/from-platform.d.ts.map +1 -1
- package/dist/lib/server/decisioning/runtime/from-platform.js +14 -1
- package/dist/lib/server/decisioning/runtime/from-platform.js.map +1 -1
- package/dist/lib/server/responses.d.ts +1 -1
- package/dist/lib/server/responses.d.ts.map +1 -1
- package/dist/lib/server/responses.js +5 -2
- package/dist/lib/server/responses.js.map +1 -1
- package/dist/lib/signing/types.d.ts +6 -0
- package/dist/lib/signing/types.d.ts.map +1 -1
- package/dist/lib/signing/types.js.map +1 -1
- package/dist/lib/signing/verifier.d.ts.map +1 -1
- package/dist/lib/signing/verifier.js +33 -4
- package/dist/lib/signing/verifier.js.map +1 -1
- package/dist/lib/testing/storyboard/compliance.d.ts +1 -0
- package/dist/lib/testing/storyboard/compliance.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/compliance.js +8 -2
- package/dist/lib/testing/storyboard/compliance.js.map +1 -1
- package/dist/lib/testing/storyboard/index.d.ts +1 -1
- package/dist/lib/testing/storyboard/index.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/index.js +3 -2
- package/dist/lib/testing/storyboard/index.js.map +1 -1
- package/dist/lib/testing/storyboard/probes.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/probes.js +3 -0
- package/dist/lib/testing/storyboard/probes.js.map +1 -1
- package/dist/lib/testing/storyboard/runner.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/runner.js +294 -29
- package/dist/lib/testing/storyboard/runner.js.map +1 -1
- package/dist/lib/testing/storyboard/types.d.ts +59 -0
- package/dist/lib/testing/storyboard/types.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/types.js.map +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts +4 -3
- package/dist/lib/testing/storyboard/validations.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/validations.js +26 -2
- package/dist/lib/testing/storyboard/validations.js.map +1 -1
- package/dist/lib/types/core.generated.d.ts +180 -252
- package/dist/lib/types/core.generated.d.ts.map +1 -1
- package/dist/lib/types/core.generated.js +1 -1
- package/dist/lib/types/schemas.generated.d.ts +127279 -125067
- package/dist/lib/types/schemas.generated.d.ts.map +1 -1
- package/dist/lib/types/schemas.generated.js +221 -293
- package/dist/lib/types/schemas.generated.js.map +1 -1
- package/dist/lib/utils/response-schemas.d.ts.map +1 -1
- package/dist/lib/utils/response-schemas.js +3 -0
- package/dist/lib/utils/response-schemas.js.map +1 -1
- package/dist/lib/utils/response-unwrapper.d.ts.map +1 -1
- package/dist/lib/utils/response-unwrapper.js +18 -3
- package/dist/lib/utils/response-unwrapper.js.map +1 -1
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/examples/error-compliant-server.ts +1 -1
- package/examples/hello_seller_adapter_guaranteed.ts +8 -3
- package/examples/hello_seller_adapter_multi_tenant.ts +27 -23
- package/examples/hello_seller_adapter_non_guaranteed.ts +7 -3
- package/examples/hello_seller_adapter_proposal_mode.ts +22 -6
- package/examples/hello_signals_adapter_marketplace.ts +34 -3
- package/package.json +1 -1
|
@@ -40,6 +40,7 @@ const client_2 = require("../client");
|
|
|
40
40
|
const idempotency_1 = require("../../utils/idempotency");
|
|
41
41
|
const schema_loader_1 = require("../../validation/schema-loader");
|
|
42
42
|
const probes_1 = require("./probes");
|
|
43
|
+
const capabilities_types_1 = require("../../signing/agent-resolver/capabilities-types");
|
|
43
44
|
const test_kit_1 = require("./test-kit");
|
|
44
45
|
const loader_1 = require("./loader");
|
|
45
46
|
const probe_dispatch_1 = require("./request-signing/probe-dispatch");
|
|
@@ -179,6 +180,107 @@ function evaluateCapabilityPredicate(predicate, actual) {
|
|
|
179
180
|
function buildSkip(reason, detail) {
|
|
180
181
|
return { reason, detail: detail ?? SKIP_DETAILS[reason] };
|
|
181
182
|
}
|
|
183
|
+
function detectResponseDerivedNotApplicable(step, request, response, runState, allSteps) {
|
|
184
|
+
if (response === undefined || response === null)
|
|
185
|
+
return null;
|
|
186
|
+
const gates = [
|
|
187
|
+
...normalizeResponseNotApplicableGates(step.not_applicable_if),
|
|
188
|
+
...inferImplicitResponseNotApplicableGates(step, request, runState, allSteps),
|
|
189
|
+
];
|
|
190
|
+
for (const gate of gates) {
|
|
191
|
+
if (gate.kind !== 'terminal_page')
|
|
192
|
+
continue;
|
|
193
|
+
if (!terminalPageGateMatches(gate, request, response))
|
|
194
|
+
continue;
|
|
195
|
+
const detail = gate.detail ??
|
|
196
|
+
`${gate.reason ?? 'single_page_result'}: ${step.task} response is terminal; cursor-walk not applicable`;
|
|
197
|
+
return {
|
|
198
|
+
detail,
|
|
199
|
+
contextKeys: responseNotApplicableContextKeys(step, gate),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
function normalizeResponseNotApplicableGates(gates) {
|
|
205
|
+
if (!gates)
|
|
206
|
+
return [];
|
|
207
|
+
return Array.isArray(gates) ? gates : [gates];
|
|
208
|
+
}
|
|
209
|
+
function inferImplicitResponseNotApplicableGates(step, request, runState, allSteps) {
|
|
210
|
+
// Back-compat for the already-published pagination_integrity_list_accounts
|
|
211
|
+
// storyboard: it expresses the continuation requirement as validations
|
|
212
|
+
// rather than a dedicated response-derived gate. Keep this narrowly scoped
|
|
213
|
+
// to list_accounts so other seeded pagination storyboards do not silently
|
|
214
|
+
// waive fixture/setup mistakes.
|
|
215
|
+
if (step.task !== 'list_accounts')
|
|
216
|
+
return [];
|
|
217
|
+
const expectsContinuation = step.validations?.some(v => v.check === 'field_value' && v.path === 'pagination.has_more' && v.value === true);
|
|
218
|
+
const capturesCursor = step.context_outputs?.some(o => o.path === 'pagination.cursor');
|
|
219
|
+
const validatesCursor = step.validations?.some(v => v.check === 'field_present' && v.path === 'pagination.cursor');
|
|
220
|
+
if (!expectsContinuation || (!capturesCursor && !validatesCursor))
|
|
221
|
+
return [];
|
|
222
|
+
const maxResults = (0, path_1.resolvePath)(request, 'pagination.max_results');
|
|
223
|
+
if (typeof maxResults === 'number' &&
|
|
224
|
+
Number.isFinite(maxResults) &&
|
|
225
|
+
hasUnrunAccountSeedExceedingPageSize(allSteps, runState, maxResults)) {
|
|
226
|
+
return [];
|
|
227
|
+
}
|
|
228
|
+
const setupAccounts = (0, path_1.resolvePath)(runState?.priorStepResults.get('sync_three_accounts')?.response, 'accounts');
|
|
229
|
+
if (typeof maxResults === 'number' &&
|
|
230
|
+
Number.isFinite(maxResults) &&
|
|
231
|
+
Array.isArray(setupAccounts) &&
|
|
232
|
+
setupAccounts.length > maxResults) {
|
|
233
|
+
return [];
|
|
234
|
+
}
|
|
235
|
+
return [{ kind: 'terminal_page', items_path: 'accounts', reason: 'single_page_result' }];
|
|
236
|
+
}
|
|
237
|
+
function hasUnrunAccountSeedExceedingPageSize(allSteps, runState, maxResults) {
|
|
238
|
+
return (allSteps?.some(({ step }) => {
|
|
239
|
+
if (runState?.priorStepResults.has(step.id))
|
|
240
|
+
return false;
|
|
241
|
+
if (step.task !== 'sync_accounts')
|
|
242
|
+
return false;
|
|
243
|
+
const accounts = (0, path_1.resolvePath)(step.sample_request, 'accounts');
|
|
244
|
+
return Array.isArray(accounts) && accounts.length > maxResults;
|
|
245
|
+
}) ?? false);
|
|
246
|
+
}
|
|
247
|
+
function terminalPageGateMatches(gate, request, response) {
|
|
248
|
+
const maxResults = (0, path_1.resolvePath)(request, gate.request_max_results_path ?? 'pagination.max_results');
|
|
249
|
+
if (typeof maxResults !== 'number' || !Number.isFinite(maxResults) || maxResults <= 0)
|
|
250
|
+
return false;
|
|
251
|
+
const items = (0, path_1.resolvePath)(response, gate.items_path);
|
|
252
|
+
if (!Array.isArray(items))
|
|
253
|
+
return false;
|
|
254
|
+
const pagination = (0, path_1.resolvePath)(response, 'pagination');
|
|
255
|
+
if (pagination === undefined || pagination === null)
|
|
256
|
+
return items.length < maxResults;
|
|
257
|
+
if (typeof pagination !== 'object' || Array.isArray(pagination))
|
|
258
|
+
return false;
|
|
259
|
+
const p = pagination;
|
|
260
|
+
if (p.has_more === true)
|
|
261
|
+
return false;
|
|
262
|
+
if (p.has_more !== false)
|
|
263
|
+
return false;
|
|
264
|
+
if (typeof p.total_count === 'number' && p.total_count > items.length)
|
|
265
|
+
return false;
|
|
266
|
+
if (items.length < maxResults)
|
|
267
|
+
return true;
|
|
268
|
+
return typeof p.total_count === 'number' && p.total_count <= items.length;
|
|
269
|
+
}
|
|
270
|
+
function responseNotApplicableContextKeys(step, gate) {
|
|
271
|
+
if (gate.context_keys?.length)
|
|
272
|
+
return gate.context_keys;
|
|
273
|
+
return (step.context_outputs ?? [])
|
|
274
|
+
.filter(o => o.path === 'pagination.cursor')
|
|
275
|
+
.map(o => o.key)
|
|
276
|
+
.filter((key) => typeof key === 'string' && key.length > 0);
|
|
277
|
+
}
|
|
278
|
+
function responseDerivedContextResult(runState) {
|
|
279
|
+
const entries = runState.responseDerivedNotApplicableContextKeys;
|
|
280
|
+
return entries && entries.size > 0
|
|
281
|
+
? { response_derived_not_applicable_context_keys: Object.fromEntries(entries) }
|
|
282
|
+
: {};
|
|
283
|
+
}
|
|
182
284
|
/**
|
|
183
285
|
* True for skip reasons that imply state genuinely never materialized
|
|
184
286
|
* — no other code path could have established it. The runner trips
|
|
@@ -1394,6 +1496,7 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
|
|
|
1394
1496
|
const contextProvenance = new Map();
|
|
1395
1497
|
const priorA2aEnvelopes = new Map();
|
|
1396
1498
|
const stepRequestStarts = new Map();
|
|
1499
|
+
const responseDerivedNotApplicableContextKeys = new Map();
|
|
1397
1500
|
const phaseResults = [];
|
|
1398
1501
|
let passedCount = 0;
|
|
1399
1502
|
let failedCount = 0;
|
|
@@ -1863,6 +1966,7 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
|
|
|
1863
1966
|
contextProvenance,
|
|
1864
1967
|
priorA2aEnvelopes,
|
|
1865
1968
|
stepRequestStarts,
|
|
1969
|
+
responseDerivedNotApplicableContextKeys,
|
|
1866
1970
|
agentLibraryVersion: profile?.library_version,
|
|
1867
1971
|
});
|
|
1868
1972
|
const result = { ...rawResult, storyboard_id: storyboard.id };
|
|
@@ -2612,6 +2716,7 @@ async function runStoryboardStep(agentUrl, storyboard, stepId, options = {}) {
|
|
|
2612
2716
|
// previous step's result). Storyboard-level runs build this internally;
|
|
2613
2717
|
// here the caller owns accumulation across stateless invocations.
|
|
2614
2718
|
const contextProvenance = new Map(Object.entries(options.context_provenance ?? {}));
|
|
2719
|
+
const responseDerivedNotApplicableContextKeys = new Map(Object.entries(options.response_derived_not_applicable_context_keys ?? {}));
|
|
2615
2720
|
const result = await executeStep(client, found.step, found.phaseId, context, allSteps, options, {
|
|
2616
2721
|
contributions: new Set(),
|
|
2617
2722
|
priorStepResults: new Map(),
|
|
@@ -2622,6 +2727,7 @@ async function runStoryboardStep(agentUrl, storyboard, stepId, options = {}) {
|
|
|
2622
2727
|
contextProvenance,
|
|
2623
2728
|
priorA2aEnvelopes: new Map(),
|
|
2624
2729
|
stepRequestStarts: new Map(),
|
|
2730
|
+
responseDerivedNotApplicableContextKeys,
|
|
2625
2731
|
agentLibraryVersion: profile?.library_version,
|
|
2626
2732
|
});
|
|
2627
2733
|
if (!options._client) {
|
|
@@ -2642,6 +2748,7 @@ client, step, phaseId, context, allSteps, options, state) {
|
|
|
2642
2748
|
agentUrl: '',
|
|
2643
2749
|
contextProvenance: new Map(),
|
|
2644
2750
|
stepRequestStarts: new Map(),
|
|
2751
|
+
responseDerivedNotApplicableContextKeys: new Map(),
|
|
2645
2752
|
};
|
|
2646
2753
|
// HTTP probe tasks bypass the MCP client entirely.
|
|
2647
2754
|
if (probes_1.PROBE_TASKS.has(step.task)) {
|
|
@@ -2810,45 +2917,50 @@ client, step, phaseId, context, allSteps, options, state) {
|
|
|
2810
2917
|
const unresolvedVars = findUnresolvedContextVars(request);
|
|
2811
2918
|
if (unresolvedVars.length > 0 && !step.expect_error) {
|
|
2812
2919
|
const next = getNextStepPreview(step.id, allSteps, context, runState.runnerVars);
|
|
2813
|
-
const
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
//
|
|
2821
|
-
//
|
|
2822
|
-
|
|
2920
|
+
const responseDerivedDetails = unresolvedVars
|
|
2921
|
+
.map(v => runState.responseDerivedNotApplicableContextKeys?.get(v.key))
|
|
2922
|
+
.filter((d) => typeof d === 'string');
|
|
2923
|
+
const allResponseDerived = responseDerivedDetails.length === unresolvedVars.length && responseDerivedDetails.length > 0;
|
|
2924
|
+
const detail = allResponseDerived
|
|
2925
|
+
? [...new Set(responseDerivedDetails)].join('; ')
|
|
2926
|
+
: `Skipped: unresolved context variables from prior steps: ${unresolvedVars.map(v => v.key).join(', ')}.`;
|
|
2927
|
+
// Normal unresolved substitutions carry one validation result per missing
|
|
2928
|
+
// token. Response-derived terminal-page skips are already successful
|
|
2929
|
+
// not_applicable rows, so their downstream cursor consumers stay validation
|
|
2930
|
+
// empty to avoid inventing a failing-looking check for an expected skip.
|
|
2823
2931
|
const synthesized = [];
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2932
|
+
if (!allResponseDerived) {
|
|
2933
|
+
const seenTokens = new Set();
|
|
2934
|
+
for (const v of unresolvedVars) {
|
|
2935
|
+
if (seenTokens.has(v.token))
|
|
2936
|
+
continue;
|
|
2937
|
+
seenTokens.add(v.token);
|
|
2938
|
+
synthesized.push({
|
|
2939
|
+
check: 'unresolved_substitution',
|
|
2940
|
+
passed: false,
|
|
2941
|
+
description: `request token "${v.token}" did not resolve — prior step did not populate context.${v.key}`,
|
|
2942
|
+
json_pointer: null,
|
|
2943
|
+
expected: v.token,
|
|
2944
|
+
actual: null,
|
|
2945
|
+
schema_id: null,
|
|
2946
|
+
schema_url: null,
|
|
2947
|
+
});
|
|
2948
|
+
}
|
|
2838
2949
|
}
|
|
2839
2950
|
return {
|
|
2840
2951
|
step_id: step.id,
|
|
2841
2952
|
phase_id: phaseId,
|
|
2842
2953
|
title: step.title,
|
|
2843
2954
|
task: step.task,
|
|
2844
|
-
passed:
|
|
2955
|
+
passed: allResponseDerived,
|
|
2845
2956
|
skipped: true,
|
|
2846
|
-
skip_reason: 'prerequisite_failed',
|
|
2847
|
-
skip: buildSkip('prerequisite_failed', detail),
|
|
2957
|
+
skip_reason: allResponseDerived ? 'not_applicable' : 'prerequisite_failed',
|
|
2958
|
+
skip: buildSkip(allResponseDerived ? 'not_applicable' : 'prerequisite_failed', detail),
|
|
2848
2959
|
duration_ms: 0,
|
|
2849
2960
|
validations: synthesized,
|
|
2850
2961
|
context,
|
|
2851
|
-
|
|
2962
|
+
...responseDerivedContextResult(runState),
|
|
2963
|
+
...(!allResponseDerived && { error: detail }),
|
|
2852
2964
|
next,
|
|
2853
2965
|
extraction: { path: 'none' },
|
|
2854
2966
|
};
|
|
@@ -3203,6 +3315,32 @@ client, step, phaseId, context, allSteps, options, state) {
|
|
|
3203
3315
|
if (step.expect_error && !taskResult?.data && taskResult?.error) {
|
|
3204
3316
|
taskResult = { ...taskResult, data: { error: taskResult.error } };
|
|
3205
3317
|
}
|
|
3318
|
+
const responseDerivedSkip = detectResponseDerivedNotApplicable(effectiveStep, request, taskResult?.data, runState, allSteps);
|
|
3319
|
+
if (responseDerivedSkip && !step.expect_error) {
|
|
3320
|
+
for (const key of responseDerivedSkip.contextKeys) {
|
|
3321
|
+
runState.responseDerivedNotApplicableContextKeys?.set(key, responseDerivedSkip.detail);
|
|
3322
|
+
}
|
|
3323
|
+
const next = getNextStepPreview(step.id, allSteps, context, runState.runnerVars);
|
|
3324
|
+
return {
|
|
3325
|
+
step_id: step.id,
|
|
3326
|
+
phase_id: phaseId,
|
|
3327
|
+
title: step.title,
|
|
3328
|
+
task: step.task,
|
|
3329
|
+
passed: true,
|
|
3330
|
+
skipped: true,
|
|
3331
|
+
skip_reason: 'not_applicable',
|
|
3332
|
+
skip: buildSkip('not_applicable', responseDerivedSkip.detail),
|
|
3333
|
+
duration_ms: stepResult.duration_ms,
|
|
3334
|
+
validations: [],
|
|
3335
|
+
context,
|
|
3336
|
+
...responseDerivedContextResult(runState),
|
|
3337
|
+
response: (0, redact_secrets_1.redactSecrets)(taskResult?.data),
|
|
3338
|
+
next,
|
|
3339
|
+
request: requestRecord,
|
|
3340
|
+
...(responseRecord && { response_record: responseRecord }),
|
|
3341
|
+
extraction: extractionFromTaskResult(taskResult),
|
|
3342
|
+
};
|
|
3343
|
+
}
|
|
3206
3344
|
// Determine pass/fail — inverted when expect_error is set
|
|
3207
3345
|
let passed;
|
|
3208
3346
|
if (step.expect_error) {
|
|
@@ -3370,6 +3508,9 @@ client, step, phaseId, context, allSteps, options, state) {
|
|
|
3370
3508
|
if (passed && hasData && taskResult) {
|
|
3371
3509
|
const extracted = (0, context_1.extractContextWithProvenance)(effectiveStep.task, taskResult.data, step.id);
|
|
3372
3510
|
Object.assign(updatedContext, extracted.values);
|
|
3511
|
+
for (const key of Object.keys(extracted.values)) {
|
|
3512
|
+
runState.responseDerivedNotApplicableContextKeys?.delete(key);
|
|
3513
|
+
}
|
|
3373
3514
|
if (runState.contextProvenance) {
|
|
3374
3515
|
for (const [key, entry] of Object.entries(extracted.provenance)) {
|
|
3375
3516
|
runState.contextProvenance.set(key, entry);
|
|
@@ -3387,6 +3528,10 @@ client, step, phaseId, context, allSteps, options, state) {
|
|
|
3387
3528
|
// ensures the minted value from any same-step $generate:…#<key> inline
|
|
3388
3529
|
// substitution is visible here.
|
|
3389
3530
|
if (step.context_outputs?.length) {
|
|
3531
|
+
for (const output of step.context_outputs) {
|
|
3532
|
+
if (output.key)
|
|
3533
|
+
runState.responseDerivedNotApplicableContextKeys?.delete(output.key);
|
|
3534
|
+
}
|
|
3390
3535
|
// Resolve `task_completion.<path>` outputs against the eventual task
|
|
3391
3536
|
// artifact rather than the immediate response. When the immediate
|
|
3392
3537
|
// response is a submitted-arm envelope (HITL / async-signed-IO flows),
|
|
@@ -3413,6 +3558,9 @@ client, step, phaseId, context, allSteps, options, state) {
|
|
|
3413
3558
|
const remappedOutputs = remapTaskCompletionOutputs(step.context_outputs);
|
|
3414
3559
|
const explicit = (0, context_1.applyContextOutputsWithProvenance)(extractionData, remappedOutputs, step.id, effectiveStep.task, updatedContext);
|
|
3415
3560
|
Object.assign(updatedContext, explicit.values);
|
|
3561
|
+
for (const key of Object.keys(explicit.values)) {
|
|
3562
|
+
runState.responseDerivedNotApplicableContextKeys?.delete(key);
|
|
3563
|
+
}
|
|
3416
3564
|
if (runState.contextProvenance) {
|
|
3417
3565
|
for (const [key, entry] of Object.entries(explicit.provenance)) {
|
|
3418
3566
|
runState.contextProvenance.set(key, entry);
|
|
@@ -3543,6 +3691,7 @@ client, step, phaseId, context, allSteps, options, state) {
|
|
|
3543
3691
|
runState.contextProvenance.size > 0 && {
|
|
3544
3692
|
context_provenance: Object.fromEntries(runState.contextProvenance),
|
|
3545
3693
|
}),
|
|
3694
|
+
...responseDerivedContextResult(runState),
|
|
3546
3695
|
error: step.expect_error ? undefined : truncateError(stepResult.error || taskResult?.error),
|
|
3547
3696
|
...(!step.expect_error && taskResult?.adcp_error && { adcp_error: taskResult.adcp_error }),
|
|
3548
3697
|
next,
|
|
@@ -3559,7 +3708,24 @@ async function executeProbeStep(step, phaseId, context, allSteps, options, runSt
|
|
|
3559
3708
|
const start = Date.now();
|
|
3560
3709
|
let httpResult;
|
|
3561
3710
|
const probeOpts = { allowPrivateIp: options.allow_http === true };
|
|
3562
|
-
if (step.
|
|
3711
|
+
if (step.requires_contract) {
|
|
3712
|
+
const contracts = new Set(options.contracts ?? []);
|
|
3713
|
+
if (!contracts.has(step.requires_contract)) {
|
|
3714
|
+
httpResult = {
|
|
3715
|
+
url: runState.agentUrl,
|
|
3716
|
+
status: 0,
|
|
3717
|
+
headers: {},
|
|
3718
|
+
body: null,
|
|
3719
|
+
skipped: true,
|
|
3720
|
+
skip_reason: 'missing_test_kit_contract',
|
|
3721
|
+
error: `Test-kit contract "${step.requires_contract}" is not configured on this runner.`,
|
|
3722
|
+
};
|
|
3723
|
+
}
|
|
3724
|
+
}
|
|
3725
|
+
if (httpResult) {
|
|
3726
|
+
// Contract-gated synthetic probes self-skip before doing any network work.
|
|
3727
|
+
}
|
|
3728
|
+
else if (step.task === 'protected_resource_metadata') {
|
|
3563
3729
|
httpResult = await (0, probes_1.probeProtectedResourceMetadata)(runState.agentUrl, probeOpts);
|
|
3564
3730
|
// RFC 9728 presence semantics (adcp-client#677): a 404 means the agent is
|
|
3565
3731
|
// honestly not advertising OAuth. Convert to a clean step skip so the
|
|
@@ -3585,6 +3751,21 @@ async function executeProbeStep(step, phaseId, context, allSteps, options, runSt
|
|
|
3585
3751
|
else if (step.task === 'request_signing_probe') {
|
|
3586
3752
|
httpResult = await (0, probe_dispatch_1.probeRequestSigningVector)(step.id, runState.agentUrl, options);
|
|
3587
3753
|
}
|
|
3754
|
+
else if (step.task === 'fetch_brand_jwks') {
|
|
3755
|
+
httpResult = await probeBrandJwks(options._profile?.raw_capabilities, probeOpts);
|
|
3756
|
+
}
|
|
3757
|
+
else if (step.task === 'assert_jwks_purpose') {
|
|
3758
|
+
httpResult = assertJwksPurpose(runState.priorProbes.get('fetch_brand_jwks'), 'webhook-signing');
|
|
3759
|
+
}
|
|
3760
|
+
else if (step.task === 'expect_rate_limit_not_replayed') {
|
|
3761
|
+
httpResult = {
|
|
3762
|
+
url: runState.agentUrl,
|
|
3763
|
+
status: 0,
|
|
3764
|
+
headers: {},
|
|
3765
|
+
body: null,
|
|
3766
|
+
error: 'rate_limit_trip_runner contract is configured, but this SDK runner does not yet implement live rate-limit trip/replay probing.',
|
|
3767
|
+
};
|
|
3768
|
+
}
|
|
3588
3769
|
if (httpResult)
|
|
3589
3770
|
runState.priorProbes.set(step.task, httpResult);
|
|
3590
3771
|
const duration = Date.now() - start;
|
|
@@ -3670,6 +3851,90 @@ async function executeProbeStep(step, phaseId, context, allSteps, options, runSt
|
|
|
3670
3851
|
extraction,
|
|
3671
3852
|
};
|
|
3672
3853
|
}
|
|
3854
|
+
async function probeBrandJwks(rawCapabilities, options) {
|
|
3855
|
+
const brandJsonUrl = (0, capabilities_types_1.readBrandJsonUrl)(rawCapabilities);
|
|
3856
|
+
if (!brandJsonUrl) {
|
|
3857
|
+
return {
|
|
3858
|
+
url: '',
|
|
3859
|
+
status: 0,
|
|
3860
|
+
headers: {},
|
|
3861
|
+
body: null,
|
|
3862
|
+
error: 'identity.brand_json_url missing from get_adcp_capabilities; cannot fetch brand JWKS',
|
|
3863
|
+
};
|
|
3864
|
+
}
|
|
3865
|
+
const brand = await (0, probes_1.fetchProbe)(brandJsonUrl, options);
|
|
3866
|
+
if (brand.error || brand.status < 200 || brand.status >= 300) {
|
|
3867
|
+
return {
|
|
3868
|
+
...brand,
|
|
3869
|
+
error: brand.error ?? `brand.json fetch returned HTTP ${brand.status}`,
|
|
3870
|
+
};
|
|
3871
|
+
}
|
|
3872
|
+
const agents = brand.body && typeof brand.body === 'object' ? brand.body.agents : undefined;
|
|
3873
|
+
const jwksUri = Array.isArray(agents)
|
|
3874
|
+
? agents
|
|
3875
|
+
.map(agent => (agent && typeof agent === 'object' ? agent.jwks_uri : undefined))
|
|
3876
|
+
.find((uri) => typeof uri === 'string' && uri.length > 0)
|
|
3877
|
+
: undefined;
|
|
3878
|
+
if (!jwksUri) {
|
|
3879
|
+
return {
|
|
3880
|
+
url: brandJsonUrl,
|
|
3881
|
+
status: 0,
|
|
3882
|
+
headers: {},
|
|
3883
|
+
body: brand.body,
|
|
3884
|
+
error: 'brand.json agents[] did not contain a jwks_uri',
|
|
3885
|
+
};
|
|
3886
|
+
}
|
|
3887
|
+
const jwks = await (0, probes_1.fetchProbe)(jwksUri, options);
|
|
3888
|
+
if (jwks.error || jwks.status < 200 || jwks.status >= 300) {
|
|
3889
|
+
return {
|
|
3890
|
+
...jwks,
|
|
3891
|
+
error: jwks.error ?? `JWKS fetch returned HTTP ${jwks.status}`,
|
|
3892
|
+
};
|
|
3893
|
+
}
|
|
3894
|
+
return jwks;
|
|
3895
|
+
}
|
|
3896
|
+
function assertJwksPurpose(prior, purpose) {
|
|
3897
|
+
if (!prior || prior.error) {
|
|
3898
|
+
return {
|
|
3899
|
+
url: prior?.url ?? '',
|
|
3900
|
+
status: prior?.status ?? 0,
|
|
3901
|
+
headers: prior?.headers ?? {},
|
|
3902
|
+
body: prior?.body ?? null,
|
|
3903
|
+
error: prior?.error ?? 'fetch_brand_jwks step missing; cannot assert JWKS purpose',
|
|
3904
|
+
};
|
|
3905
|
+
}
|
|
3906
|
+
const keys = prior.body && typeof prior.body === 'object' ? prior.body.keys : undefined;
|
|
3907
|
+
if (!Array.isArray(keys)) {
|
|
3908
|
+
return {
|
|
3909
|
+
url: prior.url,
|
|
3910
|
+
status: 0,
|
|
3911
|
+
headers: {},
|
|
3912
|
+
body: prior.body,
|
|
3913
|
+
error: 'JWKS body does not contain keys[]',
|
|
3914
|
+
};
|
|
3915
|
+
}
|
|
3916
|
+
const matching = keys.filter(key => {
|
|
3917
|
+
if (!key || typeof key !== 'object')
|
|
3918
|
+
return false;
|
|
3919
|
+
const rec = key;
|
|
3920
|
+
return rec.adcp_use === purpose && rec.status !== 'revoked' && rec.revoked !== true;
|
|
3921
|
+
});
|
|
3922
|
+
if (matching.length === 0) {
|
|
3923
|
+
return {
|
|
3924
|
+
url: prior.url,
|
|
3925
|
+
status: 0,
|
|
3926
|
+
headers: {},
|
|
3927
|
+
body: prior.body,
|
|
3928
|
+
error: `JWKS contains no active key with adcp_use="${purpose}"`,
|
|
3929
|
+
};
|
|
3930
|
+
}
|
|
3931
|
+
return {
|
|
3932
|
+
url: prior.url,
|
|
3933
|
+
status: 200,
|
|
3934
|
+
headers: prior.headers,
|
|
3935
|
+
body: { purpose, matching_key_count: matching.length },
|
|
3936
|
+
};
|
|
3937
|
+
}
|
|
3673
3938
|
function findPriorProbe(priorStepResults) {
|
|
3674
3939
|
// Fallback for runStoryboardStep where priorProbes isn't populated — reach
|
|
3675
3940
|
// into the step result's response, which we set to the HttpProbeResult above.
|