chore: release package#672
Merged
Merged
Conversation
ac9d6e6 to
3e174b5
Compare
3e174b5 to
96c309d
Compare
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.
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.
Releases
@adcp/client@5.8.0
Minor Changes
809d02e:
adcp storyboard rungains--invariants <module[,module...]>. The flagdynamic-imports each specifier before the runner resolves
storyboard.invariants, giving operators a way to populate the assertionregistry (adcp#2639) without editing the CLI. Relative paths resolve against
the current directory; bare specifiers resolve as npm packages.
Modules are expected to call
registerAssertion(...)at import time. Theflag runs before the
--dry-rungate so bad specifiers surface immediatelyduring preview, not after agent resolution and auth.
Applies to
adcp storyboard run,adcp comply(deprecated alias), andadcp storyboard run --urlmulti-instance dispatch.46de887: Add
createComplyControllerto@adcp/client/testing— a domain-groupedseller-side scaffold for the
comply_test_controllertool. Takes typedseed/force/simulateadapters and returns{ toolDefinition, handle, handleRaw, register }so a seller can wire the tool with a singlecontroller.register(server)call.The helper owns scenario dispatch, param validation, typed error
envelopes (
UNKNOWN_SCENARIO,INVALID_PARAMS,FORBIDDEN), MCPresponse shaping, and seed re-seed idempotency (same id + equivalent
fixture returns
previous_state: "existing"; divergent fixture returnsINVALID_PARAMSwithout touching the adapter). Transition enforcementstays adapter-side so the controller and the production path share a
single state machine.
Hardened against common misuse: sandbox gate requires strict
=== true(a gate that returns a truthy non-boolean denies, not allows); fixture
keys
__proto__/constructor/prototypeare rejected withINVALID_PARAMS; the default seed-fixture cache is capped at 1000net-new keys to bound memory under adversarial seeding; and the
toolDefinition.inputSchemais shallow-copied so multiple controllerson one process don't share a mutable shape.
list_scenariosbypasses the sandbox gate so capability probes alwayssucceed — buyer tooling can distinguish "controller exists but locked"
from "controller missing", while state-mutating scenarios remain gated.
register()emits aconsole.warnwhen nosandboxGateis configuredand no
ADCP_SANDBOX=1/ADCP_COMPLY_CONTROLLER_UNGATED=1env flag isset, so silent fail-open misuse becomes loud without breaking the
optional-gate API shape.
Also extends
TestControllerStorewith the five seed methods(
seedProduct,seedPricingOption,seedCreative,seedPlan,seedMediaBuy) and exportsSEED_SCENARIOS,SeedScenario,SeedFixtureCache, andcreateSeedFixtureCache. ExistingregisterTestControllercallers now pick up the seed surface and aninternal idempotency cache for free. Closes feat(testing): SDK scaffold for comply_test_controller handler #701.
d8fd93f: Add
runConformance(agentUrl, opts)— property-based fuzzing against anagent's published JSON schemas, exposed as a new
@adcp/client/conformancesubpath export so
fast-checkand the schema bundle stay off the runtimeclient path. Closes Conformance fuzzing harness: property-based tests against published JSON schemas #691.
Under the hood:
fast-checkarbitraries derived from the bundled draft-07schemas at
schemas/cache/latest/bundled/, paired with a two-path oraclethat classifies every response as accepted (validates the response
schema), rejected (well-formed AdCP error envelope with a spec-enum
reason code — the accepted rejection shape), or invalid (schema
mismatch, stack-trace leak, credential echo, lowercase reason code,
mutated context, or missing reason code). Responses that cleanly reject
unknown references count as passes, not failures.
Stateless tier covers 11 discovery tools across every protocol:
get_products,list_creative_formats,list_creatives,get_media_buys,get_signals,si_get_offering,get_adcp_capabilities,tasks_list,list_property_lists,list_content_standards,get_creative_features. Self-contained-stateand referential-ID tiers are tracked for follow-up releases.
See
docs/guides/CONFORMANCE.mdfor the full options reference.7c0b146: Conformance fuzzer Phase 2 (Conformance fuzzer Phase 2: referential stateless + Tier 2 self-contained state + CLI #698) — referential tools, fixture injection,
and
adcp fuzzCLI.get_media_buy_delivery,get_property_list,get_content_standards,get_creative_delivery,tasks_get,preview_creative. Random IDsexercise the rejection surface (agents must return
REFERENCE_NOT_FOUND, not 500).RunConformanceOptions.fixturesoption. When arequest property name matches a pool (
creative_id/creative_ids,media_buy_id/media_buy_ids,list_id,task_id,plan_id,account_id,package_id/package_ids), the arbitrary draws fromfc.constantFrom(pool)instead of random strings — testing theaccepted path on referential tools.
adcp fuzz <url>CLI: new subcommand with--seed,--tools,--turn-budget,--protocol,--auth-token,--fixture name=a,b,--format human|json,--max-failures,--max-payload-bytes, and--list-tools. Exits non-zero on failure. Reproduction hint on everyfailure:
--seed <seed> --tools <tool>.adcp fuzz https://agent.example.com/mcp --seed 42 adcp fuzz https://agent.example.com/mcp --fixture creative_ids=cre_a,cre_b --format json | jqNew public exports:
REFERENTIAL_STATELESS_TOOLS,DEFAULT_TOOLS,ConformanceFixtures,SkipReason.73db0ac: Conformance fuzzer Stage 4 — creative seeding, configurable brand,
broader stack-trace detection, additionalProperties probing, and stricter
context-echo enforcement.
Coverage (A)
sync_creativesauto-seeder: preflightslist_creative_formats,picks the first format whose required assets are all of a simple type
(image, video, audio, text, url, html, javascript, css, markdown),
synthesizes placeholder values, and captures
creative_ids from theresponse. Now runs as part of
seedFixtures/autoSeed.seedBrandoption +--seed-brand <domain>CLI flag: overridesthe mutating-seeder brand reference. Defaults to
{ domain: 'conformance.example' }, which sellers with brandallowlists reject. Configurable per run.
Oracle (D)
at com.foo.Bar.method(Bar.java:42)and
at Foo.Bar() in X.cs:line 42shapes detected alongside theexisting V8/Python/Go/PHP patterns.
(
additionalProperties: true), the generator sometimes injects one(~15% frequency, single extra key from a fixed vocabulary). Exercises
the unknown-field tolerance surface — a common crash source where
agents deserialize into strict structs and reject unexpected keys.
top-level
contextproperty, dropping it entirely is now an invariantviolation. Silent tolerance preserved for tools whose response schema
omits the field.
New public exports: extended
SeederNamewith'sync_creatives',SeedOptions.brand,RunConformanceOptions.seedBrand.6b2a3b9: Conformance fuzzer Tier 3 — auto-seeding + update-tool fuzzing.
seedFixtures(agentUrl, opts)helper — creates a property list,a content-standards config, and (after a
get_productspreflight) amedia buy on the agent, captures the returned IDs, and returns a
ConformanceFixturesbag ready to pass torunConformance. Eachseeder is best-effort: failures degrade to a recorded warning and an
empty pool, never a thrown exception.
runConformance({ autoSeed: true })— runs the seeder first,merges results into
options.fixtures(explicit fixtures win onconflict), and includes Tier-3 update tools (
update_media_buy,update_property_list,update_content_standards) in the defaulttool list. The report carries
autoSeeded: booleanand aseedWarningsarray.adcp fuzz --auto-seedCLI flag.--list-toolsnow marksTier-3 tools with
(update — needs --auto-seed or --fixture). Thehuman-readable report surfaces seeded IDs and any seed warnings.
standards_idsfixture pool —content_standardsusesstandards_id, notlist_id, so it gets its own key.fuzzer creates artifacts that the agent owns. There is no teardown.
New public exports:
seedFixtures,UPDATE_TIER_TOOLS,DEFAULT_TOOLS_WITH_UPDATES, and theSeedOptions/SeedResult/SeederName/SeedWarningtypes.3de1e82: Storyboard runner now implements first-class branch-set grading, the
contributes: trueboolean shorthand, and the implicit-detection fallbackthe AdCP spec requires (adcp-client#693, adcp#2633, adcp#2646).
Authoring (parser): phases can declare
branch_set: { id, semantics }and contributing steps can use
contributes: trueas shorthand forcontributes_to: <enclosing phase's branch_set.id>. Enforced at parse time:contributes: trueis only legal inside a phase that declaresbranch_set:.A step MUST NOT set both
contributesandcontributes_to(ambiguous).contributes_to:inside a branch-set phase MUST equalbranch_set.id.Phases declaring
branch_set:MUST setoptional: true.branch_set.semanticsmust be a supported value (any_oftoday; futureall_of/at_least_nare reserved). Unknown values are rejected atparse rather than silently skipping grading.
Grading (runner): after all phases run, branch-set peers are re-graded
per the schema rule (storyboard-schema.yaml "Per-step grading in any_of
branch patterns"). Branch-set membership is resolved two ways:
Explicit
branch_set: { id, semantics: 'any_of' }declaration.Implicit fallback: an optional phase with a step declaring
contributes_to: <flag>that matches a laterassert_contribution check: any_oftarget. Keeps pre-adcp#2633 storyboards workingunchanged.
When a peer contributes the flag, non-contributing peers' failing steps are
re-labeled as
skipped: truewith a new canonical skip reasonpeer_branch_takenand the mandated detail format:Hard failures (non-optional phases and
presenceDetectedPRM 2xx paths,adcp-client#677) are exempt from re-grading — the invariants they enforce
must stand even when a peer branch contributed.
peer_branch_takenis distinct fromnot_applicable(coverage gap) andraw
failed— dashboards can tell "agent took the other branch" apartfrom "agent misbehaved." When no peer contributes, failures stay raw and
assert_contributionis the single signal that fails the storyboard.comply.tsobservation generators (check_governance+ slow-response)now guard on
!step.warnings?.lengthso re-graded moot peers don't emitstale observations.
No storyboard migration is required.
7fbbe96: Add
refs_resolvecross-step storyboard validation (adcp#2597, adcp-client#670). A new check that asserts every ref in a source set (e.g.,products[*].format_ids[*]from a priorget_products) resolves to a member of a target set (e.g.,formats[*].format_idfrom the currentlist_creative_formats), using configurablematch_keys. Supports[*]wildcard path segments via a newresolvePathAllhelper, scope filtering by key (with$agent_urlsubstitution for the agent under test), and three out-of-scope grading modes (warn,ignore,fail). Failed checks name the exact unresolved ref tuples inactual.missingand dedupe on the projected tuple so one bad ref across 50 products shows up once.runValidations()now acceptsstoryboardContexton itsValidationContextargument so cross-step checks can read prior-step outputs; existing call sites pass it through from the runner.Hardening for untrusted inputs:
resolvePathAllcaps output at 10,000 terminal values to prevent wildcard fan-out OOM from a malicious agent response shaped for exponential expansion.__proto__,constructor, andprototypeare skipped, andhasOwnPropertygates each object lookup so a storyboard path cannot surface prototype-chain state into compliance reports.scope.equalsnormalizes trailing slashes on both sides when the scope key ends inurl, so a storyboard author can pass a literal URL or$agent_urlinterchangeably.refsMatchrejects a match when either side is missing a declaredmatch_key, preventing two refs that both omit a key from fuzzy-matching on the others.4116ea5: Reference verifier now grades negative RFC 9421 conformance vectors 021–027 (adcp-client#683, follow-up to feat(storyboard): outbound-webhook conformance runner (#2426) + 9421 webhook verifier #631). Vectors 021–026 were already implemented at the library level but skipped in the conformance suite via a three-location skip-list; 027 required a new verifier rule and a builder mutator.
verifyRequestSignaturenow rejects unsigned requests whose JSON body carries a non-emptypush_notification_config.authenticationobject anywhere in the tree, returningrequest_signature_required. Applies regardless of whether the operation sits incapability.required_for, closing the downgrade path where an attacker who captured a bearer token could register webhook credentials and redirect callbacks. Scan is recursive (handles auth material nested inside arrays of pending updates), Content-Type independent (so an attacker can't evade by labeling the bodytext/plain), and bounded: body length is capped at 1 MB (oversized unsigned bodies fail closed withrequest_signature_requiredsince we can't prove absence of webhook auth within our DoS budget) and recursion is capped at depth 64 to prevent stack-blowing on pathologically nested JSON.storyboard/request-signing/builderregisters 027 as a passthrough mutator since the adversarial shape lives in the fixture body, not a programmatic mutation.test/request-signing-vectors.test.jsdeferred itscanonicalTargetUriprecompute until areplay_cache_entriespreload actually needs it. The eager call threw on non-ASCII-authority vectors inside harness setup before the verifier's own parse-time check could run.NEGATIVE_VECTORS_UNIMPLEMENTEDremoved fromtest/request-signing-vectors.test.js; grader negative-count assertions intest/request-signing-grader-e2e.test.js,test/request-signing-grader-mcp.test.js,test/request-signing-grader-vectors.test.js, andtest/request-signing-runner-integration.test.jsupdated from 26 to 27.77ea1b9: Add schema-driven validation against the bundled AdCP JSON schemas on both
the client and the server (closes adcp-client#688).
Client hooks (on the
AdcpClient/SingleAgentClientvalidationconfig, applied automatically via
TaskExecutor):validation.requests: 'strict' | 'warn' | 'off'— validate outgoingpayloads before dispatch.
strictthrowsValidationError(
code: 'VALIDATION_ERROR') with a JSON Pointer to the offending field;warnlogs to debug logs and continues. Default:warn.validation.responses: 'strict' | 'warn' | 'off'— validate incomingpayloads on receive.
strictfails the task;warnlogs and continues.Default: strict in dev/test, warn in production. Overrides the legacy
strictSchemaValidationflag when set.Server middleware (opt-in on
createAdcpServer'svalidationconfig):validation.requests: 'strict'— dispatcher returnsadcpError('VALIDATION_ERROR', …)before the handler runs.validation.responses: 'strict'— handler-returned drift surfaces as aVALIDATION_ERRORenvelope;warnlogs to the configured logger andreturns the response unchanged.
Validation uses the bundled JSON schemas shipped at
dist/lib/schemas-data/<adcp_version>/— async response variants(
-submitted,-working,-input-required) are selected by payload shape(
statusfield), matching issue Schema-driven validation: client hooks + opt-in server middleware #688's spec.additionalPropertiesisleft permissive so vendor extensions don't trip the validator. The
VALIDATION_ERRORenvelope carries the full issue list (pointer, message,keyword, schema path) under
details.issuesfor programmatic indexing.eb675dc: Add a cross-step assertion registry to the storyboard runner
(Compliance: specialism-level invariants layer on storyboard runner adcp#2639). Storyboards now accept a top-level
invariants: [id, ...]array that references assertions registered viaregisterAssertion(spec)from@adcp/client/testing. The runner resolvesthe ids at start (fails fast on unknowns), fires
onStart→onStep(per step) →
onEnd(once at the end), routes step-scoped failures intothe step's
validations[]ascheck: "assertion", and records everyresult on a new
StoryboardResult.assertions[]field. A failed assertionflips
overall_passed— assertions are gating conformance signal, notadvisory output.
New public exports from
@adcp/client/testing:registerAssertion,getAssertion,listAssertions,clearAssertionRegistry,resolveAssertions, and typesAssertionSpec,AssertionContext,AssertionResult.Assertions encode cross-step properties that per-step checks can't
express cleanly: governance denial never mutates, idempotency dedup
across replays, context never echoes secrets on error, status
transitions monotonic, and so on. The registry ships the framework;
concrete assertion modules live alongside the specialisms that own them.
No behavior change for storyboards that don't set
invariants.4981b6b: Add
SubstitutionObserver+SubstitutionEncoder— paired runner-sideand seller-side primitives for the catalog-item macro substitution rule
(spec(creative): catalog-item macro URL escaping + nested-expansion prohibition (#2558) adcp#2620) and its runtime conformance contract
(compliance: runtime conformance storyboards for catalog-item macro substitution safety (#2620) adcp#2638, test-kit
substitution-observer-runner). Closes SubstitutionObserver + SubstitutionEncoder primitives for substitution-observer-runner contract (adcontextprotocol/adcp#2638) #696.The library is available both at the root import and at the dedicated
@adcp/client/substitutionsubpath.Seller side — produce RFC 3986-conformant encoded values from
raw catalog data:
Runner side — observe a creative preview and grade substitution
per the test-kit contract:
Both surfaces share a single RFC 3986 implementation
(
encodeUnreserved,equalUnderHexCasePolicy,isUnreservedOnly) soone bug-fix path covers producer and verifier. The seven canonical
fixture vectors from
static/test-vectors/catalog-macro-substitution.jsonship asCATALOG_MACRO_VECTORSfor reuse by storyboards and tests.enforceSsrfPolicy/enforceSsrfPolicyResolvedimplement thecontract's normative deny list (IPv4 + IPv6 CIDRs, cloud metadata
hostnames, scheme allow-list, bare-IP-literal rejection in Verified
mode, DNS revalidation of every resolved address).
fetch_and_parsepins the request to the already-policy-checked address via undici's
connect.lookup, closing the DNS rebinding window betweenresolve and connect.
The observer additionally ships
assert_unreserved_only,assert_no_nested_expansion, andassert_scheme_preservedcoveringthe contract's stricter validations
(
rfc3986_unreserved_only_at_macro_position,nested_expansion_not_re_scanned,url_scheme_preserved).Custom-vector payloads (inline
raw_value+expected_encoded) areSHA-256 redacted by default in error reports per the contract's
error_report_payload_policy; canonical fixture values echoverbatim. Pass
{ include_raw_payloads: true }to any assertionhelper to override — NOT for Verified grading.
c9977e5: Add
--webhook-receiver-auto-tunnelfor webhook-grading a remote agent froma local machine. Autodetects
ngrokorcloudflaredonPATH, spawns thetunnel pointed at the receiver, extracts the public URL, plumbs it into
proxy mode, and tears the tunnel down on exit (including on SIGINT/SIGTERM).
Use
ADCP_WEBHOOK_TUNNEL="<cmd> {port}"to override detection with acustom tunnel command — the CLI passes the auto-assigned port via
{port}substitution and captures the URL behind an explicit
ADCP_TUNNEL_URL=https://…marker the custom command must emit onstdout/stderr. The marker convention avoids misrouting webhooks to docs or
diagnostic URLs that tunnel binaries often log at startup; ngrok and
cloudflared detections use vendor-pinned regexes for the same reason.
The flag is mutually exclusive with
--webhook-receiver-public-urlandany
--webhook-receivermode (auto-tunnel already implies proxy), and(like
--webhook-receiver) incompatible with--multi-instance-strategy multi-pass. Skipped during--dry-run(the conflict validation stillruns, but no tunnel is spawned).
No spec change: the tunnel forwards ordinary HTTPS to the local receiver,
so the
webhook_receiver_runnerparity invariant (loopback_mock≡proxy_urlfor the same agent emitter path) holds. Spec-compliant with thetest-kit's "MUST NOT require a specific tunnel vendor" rule — detection is
PATH-based and vendor-agnostic. A hosted rendezvous service for graders
that can't install a tunnel binary is tracked separately at
RFC: webhook-grading rendezvous service adcp#2618 (milestone 3.1.0).
a4b8eb8: Expose the storyboard-runner webhook receiver on the CLI (closes adcp-client#675).
Before this change,
adcp storyboard runcould not enable thewebhook_receiverruntime plumbing that already existed on
runStoryboard, so storyboards whosegrading depends on observing outbound webhooks —
webhook-emission,idempotency, and any sales specialism that gradeswindow_update/IO-completion flows — skipped their webhook-assertion steps with
"Test-kit contract 'webhook_receiver_runner' is not configured on this runner."even when the agent emitted fully spec-compliant signed RFC 9421 webhooks.
Three new flags on
adcp storyboard run/adcp comply:--webhook-receiver [MODE]— host an ephemeral receiver.MODEisloopback(default; binds on 127.0.0.1) orproxy(operator-suppliedpublic URL).
--webhook-receiver-port PORT— force a specific bind port; defaults toauto-assign.
--webhook-receiver-public-url URL— public HTTPS base URL forproxymode (implies
--webhook-receiver proxywhen used alone).Setting any of these activates the receiver and adds
webhook_receiver_runnerto the run's
contractsset sorequires_contractgates resolve. The flagsare also plumbed through
ComplyOptions(webhook_receiver,contracts) soprogrammatic callers of
comply()get the same behavior without dropping torunStoryboarddirectly.Patch Changes
745415f: Adds
docs/guides/VALIDATE-YOUR-AGENT.md— the operator-facing checklist coveringadcp storyboard run,adcp fuzz(Tier 1/2/3),adcp grade request-signing, multi-instance testing,--webhook-receiver, schema-driven validation hooks, custom--invariants, andSubstitutionEncoder/Observer. Cross-linked fromBUILD-AN-AGENT.mdand the repoCLAUDE.md.Ships
npm run compliance:skill-matrix(newscripts/manual-testing/run-skill-matrix.tsdriver +skill-matrix.json) which fans the existingagent-skill-storyboard.tsharness across skill × storyboard pairs with--filter,--parallel, and--stop-on-first-fail.Every
skills/build-*-agent/SKILL.mdreplaces its ad-hoc## Validationsection with a uniform## Validate Locallyblock: canonical storyboard IDs, cross-cutting bundles (security_baseline,idempotency,schema_validation,error_compliance),adcp fuzzwith per-specialism--tools, per-specialism failure decoder, and a pointer back to the operator checklist.build-retail-media-agent/SKILL.mdgainsSubstitutionEncoder.encode_for_url_contextwiring guidance for catalog-driven macro URLs.f233402:
security_baselinerunner now enforces RFC 9728 protected-resource metadata(PRM) validations whenever the agent serves PRM at all, closing a spoofing
path (adcp-client#677) where an agent with a broken OAuth metadata document
could pass the storyboard by also declaring an API key. Previously,
oauth_discovery'soptional: truesemantics swallowed failures of theresource_equals_agent_urlandhttp_status: 200checks so long as theAPI-key path carried
auth_mechanism_verified. Now:oauth_discoveryphase cleanly(step reports
skip_reason: 'oauth_not_advertised', remaining phase stepscascade-skip). API-key-only agents that don't serve PRM see no change.
wrong
resourceURL, missingauthorization_servers, or unreachableauthorization-server metadata fails the storyboard regardless of whether
the API-key path also passes.
existing swallow-on-optional behavior — the rule only tightens when the
agent is honestly advertising OAuth.
The semantic shift encoded here: the test-kit's
auth.api_keydeclarationis an opt-IN to the API-key path, not an opt-OUT of the OAuth path. An
agent that serves PRM must serve it correctly.
bce4c9d: Route storyboard steps with
omit_idempotency_key: trueon mutating tasks through the raw-HTTP MCP probe so no SDK-layer normalization can inject a key onto the wire (adcp-client#678, adcp#2607). TheskipIdempotencyAutoInjectplumbing innormalizeRequestParams,SingleAgentClient.executeAndHandle, andTaskExecutor.executeTaskalready honors the flag, but a single regression in any of those sites would silently make every SDK-speaking agent pass the missing-key conformance vector vacuously. Dispatching viarawMcpProbe(the same path already used forstep.authoverrides andprobeSignedRequest) removes the escape hatch entirely.Scope: applies when
options.protocolis'mcp'andoptions.authis absent,'bearer', or'basic'. OAuth and A2A stay on the SDK path — their dispatch requires refresh-capable tokens / a different envelope that the raw probe can't replicate — and continue to rely on the existingskipIdempotencyAutoInjectplumbing. No YAML surface change: the existingomit_idempotency_key: truefield on a mutating step is the trigger, matching how the runner already gates the runner-levelapplyIdempotencyInvariantskip.Hardening for outbound headers: bearer tokens and basic credentials are validated for CR/LF/non-printable ASCII before being placed in headers (errors name the offending field without echoing the value), empty bearer tokens fail loudly instead of silent SDK fallback, and basic-auth usernames containing
:are rejected per RFC 7617.X-Test-Session-IDis added toSECRET_KEY_PATTERNso any future code path that persists outbound headers into compliance reports redacts it automatically.0b4115f: Skill docs follow-ups from the agent-skill-storyboard harness runs:
build-seller-agent/SKILL.md§ sales-guaranteed restructured to lead with a 3-row routing table (IO signing →submittedtask envelope /creative_assignmentsempty → synchronouspending_creatives/ otherwise →activewithconfirmed_at). The old section led with "IO approval = task envelope" and fresh Claude defaulted tosubmittedfor every scenario, missing thepending_creativespath. The routing logic is now the first code block in the section.build-brand-rights-agent/SKILL.mdshrunk from 472 → 415 lines (~12%) by collapsing the duplicated idempotency andProtecting your agentcontent into pointers at the seller skill. The long skill was causing theagent-skill-storyboard.tsharness to time out before Claude wroteserver.ts.ai_generated_image+image_generation.No public-surface changes; docs-only patch.
d51c8a5: Add test-runner guardrails so a single hung test can't consume hours of CPU (fixes npm test can hang indefinitely at 90%+ CPU — no test timeout, --test-force-exit doesn't save us #680):
npm test/npm run test:lib/prepublishOnlynow pass--test-timeout=60000. A stuck test fails after 60s with a stack trace instead of spinning indefinitely at high CPU (previously--test-force-exitonly fired after the runner finished, which a spinning test never reaches)..github/workflows/ci.ymlnow declaretimeout-minutesso a runaway job is capped at its wall-clock budget instead of eating up to the GitHub Actions default six-hour ceiling.CONTRIBUTING.mdandAGENTS.mddocument thekill -QUIT <pid>tip for dumping the V8 stack when a test appears hung.d2f1021: Register vector 027 (
webhook-registration-authentication-unsigned) as a passthrough mutation in the request-signing builder. The fixture carries its adversarial shape in the vector itself (unsigned bearer-auth request withpush_notification_config.authenticationin the body) — no programmatic mutation needed, just preserve fixture bytes throughapplyTransport.This unblocks CI after the upstream compliance cache added vector 027. The verifier rule it exercises (
#webhook-security— MUST require 9421 when authentication is present in a webhook registration body) is not yet implemented; vector 027 is added to the unimplemented-verifier skip lists alongside 021–026 until the rule lands.