From c72e2d0b00c99d18e0b1690e4ff7128013e42af9 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Wed, 13 May 2026 14:18:12 -0400 Subject: [PATCH 01/12] Import Java SDK RFC and contributor roadmap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Brings the merged adcontextprotocol/adcp#4279 RFC into this repo as the seed for implementation, alongside a contributor-facing roadmap that divides the v0.1 → v1.0 work into 14 claimable tracks and lays out the pre-contributor harness scope. - docs/rfc/java-sdk-rfc.md — RFC text from the merged spec PR; relative ../docs/... links rewritten to absolute github.com/adcontextprotocol/adcp URLs since those targets live in the spec repo. - ROADMAP.md — milestones, tracks, dependency graph, 7.x deltas vs. the RFC's 6.x baseline (auth challenge, HTTP Basic, upstream-recorder, IDEMPOTENCY_IN_FLIGHT, SSRF baseline as v0.1 not v1.x), confirmed post-RFC decisions D1-D5 (repo home, JDK 21 baseline, Maven coords group=org.adcontextprotocol artifact=adcp, Sigstore confirmed, @adcp/sdk/mock-server as the storyboard CI target). - .github/CODEOWNERS — default review on every PR to @bokelley and @MichielDean. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/CODEOWNERS | 2 + ROADMAP.md | 505 +++++++++++++++++++++++++++++ docs/rfc/java-sdk-rfc.md | 672 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 1179 insertions(+) create mode 100644 .github/CODEOWNERS create mode 100644 ROADMAP.md create mode 100644 docs/rfc/java-sdk-rfc.md diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..8d7f745 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# Default owners — every PR requests review from both. +* @bokelley @MichielDean diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 0000000..4bd7284 --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,505 @@ +# Java SDK Implementation Plan + +Working plan to take the [Java SDK RFC](docs/rfc/java-sdk-rfc.md) from zero to v1.0 GA at full L0–L3 parity with `@adcp/sdk` (TS) and `adcp` (Python). + +This is a living document. Status updates land here; design decisions land in the RFC or in a follow-up RFC. + +## Confirmed decisions + +Decisions made post-RFC that supersede or refine the merged text. Each will land as a numbered ADR under `docs/adr/` during the harness phase. + +| # | Decision | Resolution | Supersedes RFC | +|---|---|---|---| +| D1 | Repo home | Work in `adcontextprotocol/adcp-sdk-java` on feature branches → PR to `main`. | RFC §Build, distribution, governance (named the repo, didn't specify cadence) | +| D2 | Java baseline | **JDK 21 only.** Drops the 17 floor. Consequences: no `*Async` mirror surface (virtual threads make sync API scale natively), no platform-thread fallback executor in `WebhookEmitter`, `ScopedValue` directly for `UpstreamRecorder` per-principal scope, no Spring Boot 2.7 long-tail support. | RFC §Architecture / Java baseline (was "Java 17 LTS"); RFC §Async model (drops the 12-method `*Async` mirror) | +| D3 | Maven coordinates | Group `org.adcontextprotocol`. Artifacts: `adcp` (main), `adcp-server`, `adcp-testing`, `adcp-spring-boot-starter`, `adcp-cli`, `adcp-reactor`, `adcp-mutiny`, `adcp-kotlin`. Base Java package `org.adcontextprotocol.adcp.*`. Sub-packages by surface (e.g. `.task`, `.server`, `.signing`, `.testing`). | RFC §Reference (named artifacts, didn't pin group / base package) | +| D4 | Protocol tarball Sigstore | Confirmed signed. Harness schema-fetcher runs `cosign verify-blob` per RFC, no checksum-only fallback path. | Confirms RFC §Schema-bundle consumption assumption | +| D5 | Reference mock-server | The `@adcp/sdk/mock-server` package — same mock-server TS uses. Storyboard CI either runs it as a Node-side-car service container or against a hosted instance. Decide deployment shape during harness Week 1. | Specifies RFC §`comply_test_controller` "shared reference mock-server" | + +## Parity baseline (as of 2026-05-13) + +The RFC tracks `@adcp/sdk` 6.x. Current state of the world: + +| SDK | Version | Notes | +|---|---|---| +| `@adcp/sdk` (TS) | 7.2.0 | RFC was authored against 6.x; 7.x added `upstream-recorder` and other surface | +| `adcp` (Python) | 4.x (beta) | Subpackages: `compat`, `decisioning`, `migrate`, `protocols`, `schemas`, `server`, `signing`, `testing`, `types`, `utils`, `validation` | +| `adcp-go` | v1.x (dev) | Reference for a third-language take | + +**Action: T0** — re-verify the RFC's parity table against `@adcp/sdk` 7.2.0 exports before v0.1 cut. The 5-artifact target still holds; the 7.x additions collapse into existing artifacts. Specifics below. + +### 7.x deltas since the RFC was written + +Read from the TS SDK CHANGELOG. Each delta lists the Java track it folds into. None invalidate the RFC; all bump scope inside an existing track. + +**Authentication / discovery** + +- **`AuthenticationRequiredError.challenge` + `probeAuthChallenge`** (7.2.0). Parses `WWW-Authenticate` on non-Bearer 401s; the parsed `{ scheme, realm?, scope?, error?, error_description? }` rides on the error so consumers can branch on auth scheme. Wired into MCP/A2A discovery and the A2A in-flight 401 path. → [`transport`](#track-3--l0-transport-mcp--a2a) + [`docs`](#track-14--docs-migration-troubleshooting). +- **HTTP Basic auth (`auth: { type: 'basic', … }`, `--auth-scheme bearer|basic`, RFC 7617)** (7.2.0). Adopters fronted by Apigee / Kong / AWS API GW / nginx `auth_basic` were unreachable on bearer-only. Java must accept user:pass auth config, validate at register-time, and inject `Authorization: Basic` via header merging (mutually exclusive with OAuth/bearer). → [`transport`](#track-3--l0-transport-mcp--a2a) + [`cli`](#track-13--cli). +- **`resolveAgentProperties` / `listAgentPropertyMap` / `canonicalizeAgentUrl`** + `AdAgentsPublisherPropertySelector` (7.2.0). Per-entry `authorization_type` dispatch over `adagents.json` (`property_ids` / `property_tags` / `inline_properties` / `publisher_properties` / `signal_ids` / `signal_tags`). Fixes a cross-SDK divergence vs. Python. Java must match Python's `_resolve_agent_properties`, not the pre-fix TS behavior. → [`multitenant`](#track-5--l2-account-store-registry-multi-tenant). +- **`validateAdAgents` with ads.txt `MANAGERDOMAIN` one-hop fallback** (7.2.0). New top-level entrypoint with `DiscoveryMethod` / `AdAgentsValidationResult` types. One hop only; `publisher → publisher` cycles rejected; `#noagents` honored. → [`multitenant`](#track-5--l2-account-store-registry-multi-tenant). +- **`MCPOAuthProvider` `allowHttp`** + RFC 9728 resource-URL handling (6.19.1). Local dev pattern. → [`transport`](#track-3--l0-transport-mcp--a2a) + [`docs`](#track-14--docs-migration-troubleshooting). + +**Conformance / storyboard surface** + +- **`storyboards_missing_tools` vs `storyboards_not_applicable` split** (7.0.0, breaking on TS). `ComplianceResult` shape changed. Java conformance harness must emit the split from day one. → [`testing`](#track-9--testing--conformance). +- **`AdcpErrorInfo.issues[]` as first-class field** (7.0.0) — per `core/error.json` 3.0 GA. → [`codegen`](#track-2--l0-types--codegen). +- **`RunnerNotice` / `NoticeCode` / `NoticeSeverity`** (7.1.0) — stable `code` values so CI gates and JUnit consumers don't parse prose `skip.detail` strings. `StoryboardResult.notices` always-present; `ComplianceResult.notices` optional and deduped by `code`. → [`testing`](#track-9--testing--conformance). +- **`ResponseSchemaValidationError` typed error class** (7.1.0) — attributes Zod schema rejects to `response_schema` and short-circuits step-scope invariants. Java equivalent: typed error from Jackson + json-schema-validator that the storyboard runner can branch on. → [`codegen`](#track-2--l0-types--codegen) + [`testing`](#track-9--testing--conformance). +- **`parallel_dispatch` step** (7.0.0) — fans out N concurrent dispatches against the same agent (`Promise.all` shape), grades the cross-response set; drives the AdCP 3.1 concurrent-retry / first-insert-wins phase of the idempotency storyboard. New check kinds (`cross_response_*`). In Java this maps to virtual threads on 21+ / bounded executor on 17–20, plus structured concurrency for the barrier. → [`testing`](#track-9--testing--conformance) + [`async-l3`](#track-6--l3-idempotency-async-tasks-webhooks). + +**Idempotency / async wire codes** + +- **`IDEMPOTENCY_IN_FLIGHT` (AdCP 3.1 wire code)** (7.0.0) — replaces legacy `SERVICE_UNAVAILABLE` for the in-flight branch; `recovery: transient`, `retry_after` derived from claim age (short for fresh, longer for slow handlers, capped at 5s). `IdempotencyCheckResult.kind === 'in-flight'` carries `retryAfterSeconds`. Java idempotency middleware must emit this and Java callers must auto-retry on `transient + retry_after`. → [`async-l3`](#track-6--l3-idempotency-async-tasks-webhooks). + +**`comply_test_controller` / requirement autodetect** + +- **`webhook_receiver` autodetect from storyboard token presence** (7.0.0). → [`comply`](#track-8--l3-comply_test_controller). +- **`request_signer` + `oauth_metadata` autodetect, pre-empts `oauth_discovery` cascade-skip** (7.1.0). → [`comply`](#track-8--l3-comply_test_controller). +- **`account-discovery` spec-conformance gate** (6.17.0) — gates around `get_adcp_capabilities` / `list_accounts` / `sync_accounts` advertisement. → [`comply`](#track-8--l3-comply_test_controller) + [`multitenant`](#track-5--l2-account-store-registry-multi-tenant). +- **Per-storyboard `requires:` gate** (`controller` / `seeded_state` / `real_wire`) + `--asserts-seeded-state` flag (6.17.0). → [`testing`](#track-9--testing--conformance). +- **Detailed skip-cause taxonomy** (`RunnerDetailedSkipReason`) (6.17.0). → [`testing`](#track-9--testing--conformance). + +**`upstream-recorder` — new artifact surface (not on the RFC)** + +- **`@adcp/sdk/upstream-recorder`** (added in 7.x). Producer-side reference middleware for the AdCP `upstream_traffic` storyboard check (spec PR #3816). Wraps the adapter's HTTP layer, redacts at record time (plaintext secrets never sit in memory), enforces per-principal isolation (spec HIGH security requirement), exposes `query()` that maps onto the controller's `UpstreamTrafficSuccess` shape. Sandbox-only by default; production-disabled is a no-op zero-overhead path. +- **Java fit:** lands in `adcp-server` as `UpstreamRecorder` SPI. Per-principal scoping uses `ScopedValue` (Java 21 baseline; see [Confirmed decisions](#confirmed-decisions)). HTTP wrapper integrates at the same boundary as outbound signing. → new sub-scope in [`async-l3`](#track-6--l3-idempotency-async-tasks-webhooks) (the recorder co-locates with webhook outbound HTTP). The RFC's `comply_test_controller` surface gains a `query_upstream_traffic` scenario served from the recorder. + +**Codegen surface tightening (TS-only output shape, applies to Java codegen too)** + +- **`Format.assets` named slot unions** (`IndividualAssetSlot` / `GroupAssetSlot` / `FormatAssetSlot`) with `asset_type` discriminator + per-slot `requirements` (6.19.0). Java codegen must emit equivalent sealed interfaces with the discriminator pattern. → [`codegen`](#track-2--l0-types--codegen). +- **`CreativeBuilderPlatform` doesn't advertise missing tools in `tools/list`** (7.0.0) — the platform doesn't lie about its capabilities. Java server-side handler must conditionally advertise tools based on adopter implementation. → [`transport`](#track-3--l0-transport-mcp--a2a). + +**Security / SSRF posture (cross-cutting hardening)** + +- The 6.16–6.17 line added a cross-cutting SSRF migration covering `detectProtocol`, `discoverAgentProfile`, `fetchAdAgentsJsonFromUrl`, the network-consistency-checker, the property-crawler, and the buyer-side discovery path. The baseline is: resolve DNS once, validate the full address set against address-guards, pin the connect to the first validated address (undici interceptor), `redirect: 'manual'`, 4 KiB body cap on probes. Java SDK must match this baseline on every outbound discovery call — not as a v1.x hardening pass, **as v0.1 baseline**. JDK `HttpClient` doesn't pin connect-addresses natively; Java needs a custom `Authenticator`/`Selector` shim or the equivalent at the `SocketChannel` boundary. → cross-cutting concern, lives in [`transport`](#track-3--l0-transport-mcp--a2a) with its own design doc before contributor pickup. + +- **`tasks/cancel` fire-and-forget on buyer abort** (6.16.0). Aborted poll must POST a real-UUID `tasks/cancel` with `AbortSignal.timeout(5000)`, silently catch rejection (an aborted buyer must not have observability dependencies on cancel success). `signed-requests` sellers will 401 unsigned cancels — so this path threads through outbound signing. → [`transport`](#track-3--l0-transport-mcp--a2a) + [`signing`](#track-4--l1-signing). + +### 7.x impact on the milestone calendar + +The 7.x deltas don't move milestones, but they do tighten the v0.1 gate: + +- v0.1 release gate **adds**: SSRF baseline (DNS pin, address-guards, redirect:manual, body cap) on all discovery probes. `WWW-Authenticate`-aware error envelope. HTTP Basic config support. The `storyboards_missing_tools` / `storyboards_not_applicable` split in the storyboard runner output. +- v0.2 gate **adds**: nothing new from 7.x — the L1 signing surface didn't grow. +- v0.3 gate **adds**: `IDEMPOTENCY_IN_FLIGHT` wire code with claim-age-derived `retry_after` cap. `resolveAgentProperties` / `validateAdAgents` / `MANAGERDOMAIN` fallback. +- v0.4 gate **adds**: `upstream-recorder` SPI, `query_upstream_traffic` controller scenario, `parallel_dispatch` storyboard step, full `RunnerNotice` taxonomy. + +## Harness first — what lands before contributors are pulled in + +Open question from the user: should we (the founder pair) build a harness before opening tracks to contributors? + +**Recommendation: yes, ~2 weeks of scaffold, then open tracks.** Empty repos lose contributors. A skeleton with "your environment compiles, your tests run, here's where to start" loop is the difference between a track claim turning into a PR vs. turning into a Slack thread. + +Hard line: **scaffold the build, leave the rooms empty.** Don't pre-build L1 / L2 / L3 surface — that locks in design before the contributors who'll own those tracks weigh in. + +### Pre-contributor harness scope + +| Item | Why | Builds toward | +|---|---|---| +| Gradle multi-module skeleton (5 published artifacts + 2 bridge modules `adcp-reactor` / `adcp-mutiny` as empty stubs) | Stable multi-module graph from day one; package names locked; contributors don't fight `settings.gradle.kts` reviews | [`infra`](#track-1--build-repo-release-infra) | +| Schema-bundle fetcher Gradle task: download `{version}.tgz`, `cosign verify-blob`, extract to build dir | Codegen has something to point at; SSRF/signing posture established before any HTTP code lands | [`infra`](#track-1--build-repo-release-infra) | +| Codegen MVP: emit records + builder records for **one or two** request/response pairs (e.g. `GetProductsRequest` / `GetProductsResponse`) | Proves the generator architecture, locks in the `*Request`/`*Response` naming invariant, gives contributors real Java to import. Full coverage stays in [`codegen`](#track-2--l0-types--codegen). | [`codegen`](#track-2--l0-types--codegen) | +| MCP SDK decision resolved (Spring AI vs. official `io.modelcontextprotocol`) + recorded in an ADR | Blocks [`transport`](#track-3--l0-transport-mcp--a2a). Drift between releases is worse than picking the less-mature option and migrating. | [`transport`](#track-3--l0-transport-mcp--a2a) | +| SSRF-safe `HttpClient` wrapper skeleton (DNS pin, address-guards, redirect:manual, body cap) | Baseline 7.x security posture. JDK `HttpClient` doesn't pin natively; this needs a design doc + skeleton before contributors touch outbound HTTP. | [`transport`](#track-3--l0-transport-mcp--a2a) | +| Storyboard CI gate shell: GitHub Actions on JDK 21, runs the runner against the `@adcp/sdk/mock-server`, even if the runner currently asserts only "we reached the server" | The v0.1 release gate is "storyboards green in CI." Standing it up empty and having it pass keeps contributors honest as L0 fills in — every PR is measured against the gate. | [`infra`](#track-1--build-repo-release-infra) + [`testing`](#track-9--testing--conformance) | +| Repo conventions: `CONTRIBUTING.md` (track-claim flow), `.github/ISSUE_TEMPLATE/track-claim.md`, PR template, `CLAUDE.md` for agent contributors | The track-claim issue template is the actual contributor onboarding doc | [`docs`](#track-14--docs-migration-troubleshooting) | +| ADR directory + first three ADRs (Java baseline, async model, MCP SDK choice) | RFC decisions captured in the format contributors expect to amend | [`docs`](#track-14--docs-migration-troubleshooting) | + +### Explicitly **not** in the harness + +These look tempting to "get started on" but pre-building them locks in design that should be a track owner's call: + +- L1 RFC 9421 signing — too much spec surface; lives in [`signing`](#track-4--l1-signing). +- Full type generation — [`codegen`](#track-2--l0-types--codegen)'s job once MVP is proven. +- Spring Boot starter — downstream, blocks on L1/L2/L3 surface. +- Account store / idempotency / webhook code — design-heavy; needs the track owner's voice. +- Lifecycle YAML coordination — depends on TS/Python maintainer buy-in (RFC Decision 6). + +### Ordering + +- **Week 1 (founder pair):** Gradle skeleton, schema fetcher, codegen MVP, repo conventions, ADR directory, CI shell. +- **Week 2 (founder pair + advisors):** MCP SDK pick + ADR. SSRF wrapper skeleton + design doc. First storyboard CI run green-against-empty. Open the first 3–4 track-claim issues publicly. +- **Week 3+:** Contributors arrive against a repo where `./gradlew check` passes and CI tells them whether their PR broke conformance. Tracks land in dependency order. + +This means the founder pair owns the [`infra`](#track-1--build-repo-release-infra) track end-to-end and the first slice of [`codegen`](#track-2--l0-types--codegen) and [`transport`](#track-3--l0-transport-mcp--a2a). Everything else opens for claim once the harness is green. + +## How to read this plan + +The roadmap below has two axes: + +- **Milestones** (v0.1 → v1.0) — vertical slices tied to the RFC roadmap. Each milestone has a date target (M+N from project kickoff) and a release gate. +- **Tracks** — parallel workstreams that contributors can claim independently. A track spans multiple milestones. + +Contributors claim a **track**, not a milestone. Tracks have explicit dependencies; if track A blocks track B at milestone N, that's called out so we don't fail to sequence. + +Each track entry has: + +- **Scope** — what's in. +- **Out of scope** — what isn't, to keep the track bounded. +- **Depends on** — tracks that must land first. +- **Size** — rough person-month estimate (eng-months of focused work, not calendar time). +- **Owner** — `TBD` until a contributor claims it. Claim by opening an issue with the track ID. + +## Milestones + +| Milestone | Target | Release gate | +|---|---|---| +| v0.1 alpha | M+2 | L0 surface compiles, storyboards green against reference mock-server in CI, Maven Central alpha published | +| v0.2 alpha | M+4 | L1: RFC 9421 signing/verification, AWS+GCP KMS providers (lazy-init, per-`adcp_use`), webhook signing | +| v0.3 alpha | M+6 | L2 + partial L3: account store, idempotency, async tasks, Spring Boot starter alpha | +| v0.4 beta | M+9 | Full L3: transition validators, webhook emission, `comply_test_controller`, A2A transport | +| v1.0 GA | M+12 | L0–L3 parity, Reactor + Mutiny adapters, Kotlin co-release, Maven Central GA | + +The RFC's M+12 target is the realistic line. Pre-committing M+9 and slipping is worse than committing M+12 and beating it. Slippage concentrates on: MCP Java SDK churn, RFC 9421 canonicalization edge cases, shared lifecycle YAML coordination, Spring Boot starter scope creep. + +## Tracks + +### Track 1 — Build, repo, release infra + +**ID:** `infra` | **Owner:** TBD | **Size:** 1.0 person-month across the year + +**Scope:** + +- Gradle multi-module skeleton matching the 5 artifacts (`adcp`, `adcp-server`, `adcp-testing`, `adcp-spring-boot-starter`, `adcp-cli`). +- `adcp-reactor` and `adcp-mutiny` modules wired from the start (empty until [`async-bridges`](#track-12--reactor--mutiny-adapters)) so the multi-module graph is stable. +- `Automatic-Module-Name` set on every JAR manifest. +- Gradle reproducible-jar config, lockfiles checked in. +- Codegen Gradle task that downloads the protocol tarball, verifies with `cosign verify-blob`, extracts schemas, hands off to [`codegen`](#track-2--l0-types--codegen). +- Maven Central publish via Sonatype OSSRH, GPG-signed; Sigstore migration tracked as a follow-up. +- GitHub Actions on JDK 21, run storyboard CI against `@adcp/sdk/mock-server` (the v0.1 gate; storyboard runner from [`testing`](#track-9--testing--conformance) plugs in). +- JavaDoc + sources jars on every release. + +**Out of scope:** GraalVM native-image (post-v1.0), JPMS modules (opt-in only). + +**Depends on:** nothing. This track unblocks everything else and should land in week 1. + +**Milestone targets:** v0.1 needs Gradle multi-module + Maven Central alpha publish + CI matrix. Sigstore signing migration can land any time before v1.0. + +--- + +### Track 2 — L0 types & codegen + +**ID:** `codegen` | **Owner:** TBD | **Size:** 2.0 person-months + +**Scope:** + +- Custom codegen on Eclipse JDT or JavaPoet, emitting Java records for value/response types and builder-records for request types. +- Generator invariant: `*Request` types always have builders; `*Response` types are records and never do (RFC §Type generation). +- Polymorphic envelope handling (Jackson `@JsonTypeInfo` / `@JsonSubTypes`). +- `x-adcp-*` annotation post-processors mirroring `scripts/generate-types.ts` in `adcp-client`. +- Version pinning support (`adcp-v2-5` co-existence namespace). +- JSpecify `@Nullable` annotations on every public type. No `Optional` returns. +- Schema validator wrapper around `com.networknt:json-schema-validator`. +- Schema-bundle accessor (runtime, resources jar; build-time loader lives in [`infra`](#track-1--build-repo-release-infra)). + +**Out of scope:** Kotlin source generation (handled by [`kotlin`](#track-11--kotlin-extensions)). + +**Depends on:** `infra` (codegen Gradle task hookpoint). + +**Milestone targets:** v0.1 needs full generated type coverage for the L0 surface and validator wired into transport. + +--- + +### Track 3 — L0 transport: MCP + A2A + +**ID:** `transport` | **Owner:** TBD | **Size:** 1.5 person-months + +**Scope:** + +- **MCP:** pick between Spring AI's MCP SDK and the official `io.modelcontextprotocol` (Open Question 2 in the RFC). Decide before v0.1 cut; drift between releases is worse than picking the less-mature one and migrating later. Wrap the chosen SDK with the AdCP transport surface. +- **A2A pre-1.0:** minimal SSE consumer + JSON-RPC framer in `adcp-server`. Default: keep types in-tree until `a2a-java` cuts its first stable release (≥ 1.0.0), then migrate in one shot (RFC Open Question 3). +- **A2A post-1.0:** swap transport to `a2aproject/a2a-java`; deprecate the in-tree fallback in the next minor. +- HTTP transport on `java.net.http.HttpClient`. No third-party HTTP client in the core. +- Jackson `ObjectMapper` with `StreamReadConstraints` / `StreamWriteConstraints` widened to AdCP-shaped defaults (RFC §JSON). +- **No `*Async` mirror methods.** With JDK 21 as baseline, virtual threads make the sync API scale natively; the RFC's 12-method `*Async` mirror surface is dropped (see [Confirmed decisions](#confirmed-decisions)). Adopters who explicitly want `CompletableFuture` wrap individual calls themselves. + +**Out of scope:** OkHttp / Apache HttpClient 5 adapters (post-v1.0 on demand). + +**Depends on:** `codegen` for the request/response types. + +**Milestone targets:** v0.1 needs MCP transport. v0.4 swaps in upstream `a2a-java` if its 1.0 has cut by then; otherwise the in-tree fallback ships at v1.0 with the swap-trigger documented. + +--- + +### Track 4 — L1 signing + +**ID:** `signing` | **Owner:** TBD | **Size:** 1.5 person-months + +**Scope:** + +- Hand-rolled RFC 9421 canonicalizer (it's small and spec-tight; `org.tomitribe:http-signatures` is the wrong spec). Verifier test harness mirrors the TS one. +- `SigningProvider` SPI via `META-INF/services/`. API shape: `SigningProvider.forUse(AdcpUse.WEBHOOK)` returns a distinct provider from `.forUse(AdcpUse.REQUEST)` — receivers enforce purpose at JWK `adcp_use`. +- In-process provider via JCA Ed25519 / ECDSA. **No Bouncy Castle in core** — JDK 21 has Ed25519 natively. +- AWS KMS provider via `software.amazon.awssdk:kms`. Lazy-init. +- GCP KMS provider via `com.google.cloud:google-cloud-kms`. Lazy-init. +- Optional `adcp-signing-bouncycastle` artifact for FIPS environments. +- Outbound webhook signing wired into [`async-l3`](#track-6--l3-idempotency-async-tasks-webhooks). +- Pre-deploy KMS probe as a separate CLI command, not part of boot critical path. + +**Out of scope:** Azure KMS (post-v1.0 on demand). Hardware HSM integration beyond JCA (post-v1.0). + +**Depends on:** `transport` (signing wraps HTTP-level requests). + +**Milestone targets:** v0.2 ships RFC 9421 + AWS+GCP KMS + webhook outbound signing. + +--- + +### Track 5 — L2 account store, registry, multi-tenant + +**ID:** `multitenant` | **Owner:** TBD | **Size:** 1.0 person-month + +**Scope:** + +- `AccountStore` SPI. Reference impls: + - `InMemoryAccountStore` (tests). + - `JdbcAccountStore` against a Flyway/Liquibase-managed schema. + - Optional `JpaAccountStore` if Spring Data JPA shops claim it. +- `RegistryClient` SPI for agent-registry / brand-resolution lookup. Default impl points at the public AAO registry. +- Multi-tenant principal resolution wired through the request handler. +- Sandbox/live boundary enforcement at the `AccountStore` (so `comply_test_controller` calls return `COMPLY_NOT_AVAILABLE` on production accounts per spec). +- Agent-card publication helper. + +**Out of scope:** Caller-side credential presentation beyond what L0 already covers (folded into [`transport`](#track-3--l0-transport-mcp--a2a)). + +**Depends on:** `codegen` for principal / account types. + +**Milestone targets:** v0.3 alpha ships full L2. + +--- + +### Track 6 — L3 idempotency, async tasks, webhooks + +**ID:** `async-l3` | **Owner:** TBD | **Size:** 2.0 person-months + +**Scope:** + +- `IdempotencyStore` SPI with in-memory, JDBC, Redis (Lettuce) refs. +- `IdempotencyConflict` as a **sealed type** that structurally cannot carry a payload echo (read-oracle threat model from `L1/security.mdx#idempotency`). +- Byte-identical replay within TTL: store API takes and returns raw bytes alongside the typed response so replay can't accidentally re-serialize and drift. +- `TaskStore` SPI with generic artifact type — the compiler enforces that a task's terminal artifact carries the **original tool's response shape**, not a generic task envelope. +- `WebhookEmitter` two-executor pattern (RFC §Async-task store and webhooks): + - `scheduler`: small platform-thread `ScheduledExecutorService` (default size 1–2). Pure scheduling. + - `dispatcher`: separate executor that runs HTTP delivery. Default: `Executors.newVirtualThreadPerTaskExecutor()` (JDK 21 baseline). + - Both executors injectable on `WebhookEmitter.builder()`. +- Async-result polling shape on the caller side. +- Error-recovery classification consumed from the spec's `error-code.json` `enumMetadata` (PR #3738). SDK consumes; doesn't re-derive. + +**Out of scope:** Persistence migrations beyond reference schemas (adopter responsibility). + +**Depends on:** `codegen`, `signing` (webhook outbound), `multitenant` (sandbox boundary). + +**Milestone targets:** Partial in v0.3 (idempotency + async tasks). Full webhook emitter in v0.4. + +--- + +### Track 7 — L3 lifecycle & transitions + +**ID:** `lifecycle` | **Owner:** TBD | **Size:** 1.5 person-months (Java-side; coordination cost with TS/Python maintainers is separate) + +**Scope:** + +- Decide between RFC paths 1 and 2 (RFC §Lifecycle and transition validation). Recommendation in RFC: path 2 (lead the cross-SDK shared YAML lifecycle source). Decision depends on TS + Python maintainer commitment — see [Decisions wanted](#decisions-wanted). +- If path 2: author lifecycle YAMLs in the spec repo for the 7 resources (`MediaBuy`, `Creative`, `Account`, `SISession`, `CatalogItem`, `Proposal`, `Audience`). Wire all three SDKs to consume them. +- Transition validator API takes `(action, from, to)`, not `(from, to)` — `NOT_CANCELLABLE` precedence over `INVALID_STATE` requires the action. +- `TransitionGuard` SPI for adopter preconditions. Guards run **after** the spec edge check; can never relax a spec edge. +- Guard narrowing protection: guards declare which edges they touch; conformance harness fails if a sandbox account's guards narrow any edge the storyboards exercise (RFC Open Question 7). + +**Out of scope:** Adopter-side guard implementations (L4 concern). + +**Depends on:** `codegen` for resource types, `testing` for harness integration. + +**Milestone targets:** v0.4. Path 2 coordination starts immediately if WG signs off. + +--- + +### Track 8 — L3 `comply_test_controller` + +**ID:** `comply` | **Owner:** TBD | **Size:** 0.75 person-month + +**Scope:** + +- `seed_*` / `force_*` / `simulate_*` controller surface matching `@adcp/sdk`'s `/conformance` and `/compliance`. +- Sandbox-only enforcement wired at the `AccountStore` boundary (production → `COMPLY_NOT_AVAILABLE`). +- Storyboard hint fix-plan format (`Diagnose / Locate / Fix / Verify`) surfaced in adopter-facing test reports. + +**Depends on:** `multitenant` (sandbox boundary), `codegen`. + +**Milestone targets:** v0.4. + +--- + +### Track 9 — Testing & conformance + +**ID:** `testing` | **Owner:** TBD | **Size:** 1.5 person-months + +**Scope:** + +- `adcp-testing` artifact, JUnit 5 first-class. +- `AdcpAgentExtension` — JUnit 5 extension that boots an in-process agent (or wraps an adopter's agent) for storyboard runs. +- `StoryboardRunner` — Java port of TS `runStoryboard`. Reads YAML storyboards from the protocol bundle, runs them against an agent under test, asserts wire conformance. +- **Mock-server forwarding adapter** (critical — RFC §`comply_test_controller`): storyboards certify against the shared reference mock-server, not an in-process Java mock. Without this, storyboards run against the SDK's own L4 stub instead of the spec-compliance oracle, and certification fails. +- `MockAgent` for callers under test (buyer-side mirror). +- `Personas` port of `/testing/personas`. +- Signing test fixtures (port of `/signing/testing`). + +**Out of scope:** Test infrastructure beyond JUnit 5 (TestNG support is adopter's problem if they want it). + +**Depends on:** `codegen`, `transport`. The mock-server forwarding adapter is the v0.1 release gate — without it, CI claims conformance it doesn't have. + +**Milestone targets:** v0.1 ships the storyboard runner + forwarding adapter. Conformance test surface expands at each subsequent milestone as L1/L2/L3 land. + +--- + +### Track 10 — Spring Boot starter + +**ID:** `spring` | **Owner:** TBD | **Size:** 1.0 person-month + +**Scope:** + +- `adcp-spring-boot-starter` auto-configures: handler, Jackson `ObjectMapper`, signing provider, account store. +- Micrometer `MeterRegistry` integration **if on classpath**. Metric names: `adcp.tool.duration`, `adcp.signing.verify.failures`, etc. +- Actuator `AdcpHealthIndicator` **if on classpath** — reports signing-key reachability + account-store reachability. +- Spring properties for tunables: `adcp.jackson.max-string-length`, etc. +- Spring Security integration as a **documented recipe**, not autoconfig (RFC §Server framework integration). Decision on `adcp-spring-boot-starter-security` deferred to v0.3 feedback. +- **`javax` vs `jakarta` decision** (RFC Open Question 6): default recommendation is floor at Spring Boot 3.x (`jakarta`). Confirm before v0.3 alpha — starter package layout depends on it. + +**Out of scope:** Quarkus / Micronaut / Servlet adapters (post-v1.0 on demand). + +**Depends on:** `transport`, `signing`, `multitenant`, `async-l3`. + +**Milestone targets:** Alpha in v0.3, polished by v1.0. + +--- + +### Track 11 — Kotlin extensions + +**ID:** `kotlin` | **Owner:** TBD | **Size:** 0.75 person-month + +**Scope:** + +- `adcp-kotlin` extension artifact on top of the Java surface. +- Coroutine `suspend fun` extension wrappers around the sync API. +- DSL builders for request types. +- Nullability already correct because the Java surface is JSpecify-annotated. + +**Out of scope:** Independent Kotlin SDK. Kotlin remains a thin layer on Java. + +**Depends on:** Stable Java public surface (waits for v1.0 freeze of L0–L3 APIs). + +**Milestone targets:** Co-released with v1.0. + +--- + +### Track 12 — Reactor & Mutiny adapters + +**ID:** `async-bridges` | **Owner:** TBD | **Size:** 0.5 person-month + +**Scope:** + +- `adcp-reactor` — wraps the sync surface in `Mono.fromCallable(...)` on a bounded elastic scheduler. +- `adcp-mutiny` — Quarkus equivalent. +- **Both at GA**, not fast-follow (RFC §Async model — WebFlux shops left to wrap the sync API will own that complexity forever). + +**Depends on:** Stable Java public surface. + +**Milestone targets:** v1.0. + +--- + +### Track 13 — CLI + +**ID:** `cli` | **Owner:** TBD | **Size:** 0.5 person-month + +**Scope:** + +- `adcp-cli` runnable jar. Commands: `adcp [tool] [payload]`, `adcp storyboard run`, `adcp grade`. +- Pre-deploy KMS probe command (from `signing`). +- Homebrew tap as a Java-leads add. + +**Out of scope:** GraalVM native-image (post-v1.0 — RFC §Reference). + +**Depends on:** `transport`, `signing`, `testing`. + +**Milestone targets:** Alpha in v0.3, polished by v1.0. + +--- + +### Track 14 — Docs, migration, troubleshooting + +**ID:** `docs` | **Owner:** TBD | **Size:** 1.0 person-month spread across milestones + +**Scope:** + +- JavaDoc on every public type. Generated and published. +- Migration guides for the four audiences (RFC §Migration path): + 1. Hand-rolled JVM agents. + 2. Python sidecar shops. + 3. Kotlin/JVM agents on Spring Boot. + 4. New JVM agents. +- Troubleshooting docs for the 8 RFC §Spec gotchas, especially the Jackson `StreamReadConstraints` first-hour bounce and Spring Boot 2.7 `NoSuchMethodError`. +- "Things we'd tell a new contributor" doc derived from the gotchas list. + +**Depends on:** Each track contributes its slice of docs. + +**Milestone targets:** Doc completeness gates v1.0 GA. + +## Track dependency graph + +``` +infra ──┬─→ codegen ──┬─→ transport ──┬─→ signing ──┬─→ async-l3 ──┬─→ spring + │ │ │ │ │ + │ ├──→ testing ←──┘ │ │ + │ │ │ │ + │ ├─→ multitenant ──────────────┴─→ lifecycle │ + │ │ │ │ + │ │ └─→ comply │ + │ │ │ + │ └─→ (stable surface) ─→ kotlin │ + │ ─→ async-bridges │ + │ ─→ cli ────────────────┘ + │ + └─→ docs (cross-cutting) +``` + +## Claiming a track + +Open an issue titled `[track:] claims `. Include: + +- Your prior JVM experience (records, sealed types, Jackson, Spring Boot, RFC 9421 / signing if relevant). +- Estimated availability per week. +- Which milestone you're committing to first. +- Whether you can attend the working-group sync. + +A single contributor can claim multiple non-conflicting tracks (e.g. `cli` + `docs`). High-coupling tracks (`async-l3` + `lifecycle`) benefit from a single owner pair. + +## Decisions wanted (blockers before scaling contributor count) + +In priority order, from the RFC: + +1. **Funding / staffing.** A contributed engineer at 50%+ for ~12 months, plus a named WG maintainer with merge rights, plus 2–3 design partners committed v0.1 → v0.4. Without all three, the RFC says decline and revisit at next major. +2. **Design partners.** 2–3 JVM shops with letters of intent to ship on the SDK in 2026. +3. **WG vote** on Java as the fourth officially supported language. +4. **Maintainer.** Named owner with merge rights post-GA. +5. **MCP Java SDK choice.** Spring AI vs. official `io.modelcontextprotocol`. Blocks `transport`. +6. **Cross-SDK lifecycle YAML buy-in.** TS + Python maintainers willing to consume a shared source. Decides `lifecycle` path 1 vs. path 2. +7. **`javax` vs `jakarta` floor.** Decides `spring` artifact layout. +8. **Reactor/Mutiny + Kotlin at GA vs. fast-follow** — RFC's position is "at GA"; WG can cut scope. + +## What's not in this plan (yet) + +- Hiring or sourcing the contributed engineer / design partners — that's a foundation-level conversation, not a planning artifact. +- An RFC for the cross-SDK lifecycle YAML — should be authored separately once TS + Python maintainers are at the table. +- A v1.x roadmap (Quarkus, Micronaut, Servlet, OkHttp, Apache HttpClient 5, Azure KMS, GraalVM, JPMS modules). All deferred to post-v1.0 demand. + +## Status + +| Stage | Status | +|---|---| +| RFC merged on adcontextprotocol/adcp | ✅ (PR #4279, 2026-05-13) | +| RFC imported to this repo | ✅ ([docs/rfc/java-sdk-rfc.md](docs/rfc/java-sdk-rfc.md)) | +| Implementation plan drafted | ✅ (this doc) | +| Funding / staffing confirmed | ⏳ Decision pending | +| Tracks claimed | 0 / 14 | +| Pre-contributor harness | Not Started | +| v0.1 alpha | Not Started | diff --git a/docs/rfc/java-sdk-rfc.md b/docs/rfc/java-sdk-rfc.md new file mode 100644 index 0000000..5a4ff44 --- /dev/null +++ b/docs/rfc/java-sdk-rfc.md @@ -0,0 +1,672 @@ +# Java SDK RFC + +**Status:** Draft for Builders Working Group +**Author:** Brian O'Kelley +**Created:** 2026-05-05 +**Tracks parity with:** `@adcp/sdk` 6.x (TS, GA), `adcp` 4.x (Python, beta), `adcp-go` v1.x (dev) +**Layer model:** see [SDK stack reference](https://github.com/adcontextprotocol/adcp/blob/main/docs/building/cross-cutting/sdk-stack.mdx) + +## Why + +Multiple AdCP adopters have requested a Java SDK. The JVM is the dominant +language at large publisher and ad-server platforms — GAM, FreeWheel, Magnite, +Index Exchange, PubMatic, Equativ, every broadcaster's middleware. Today those +teams either: + +- Hand-roll L0–L3 from the published JSON schemas (~3–4 person-months per the + SDK stack reference, with conformance debt every spec rev), or +- Stand up a Python sidecar in front of their JVM application — workable for a + caller, painful for an agent that needs **shared transaction context with + the existing decisioning engine** (the unique JVM win Python can't deliver). + +Neither is a good answer for the SDK's largest unaddressed audience. This RFC +proposes a first-class Java SDK targeting full L0–L3 parity, on the same +release cadence as TS and Python. + +**This RFC asks the Working Group to commit to Java as a fourth officially +supported language**, with the funding model and design-partner gating +described under [Decisions wanted](#decisions-wanted). + +## Goals + +1. **Conformance parity.** The Java SDK passes the same mock-mode storyboards + that gate `@adcp/sdk` and `adcp`, **with storyboards in CI from v0.1, not + v0.4**. Without that gate from day one, JVM teams won't trust the + conformance claim. +2. **Both-sides coverage.** Caller, agent (server), signing, testing — same + surface decomposition as TS. +3. **Idiomatic JVM.** Reads like a modern Java library, not a transliteration + of the TS or Python API. Records, sealed types, `CompletableFuture` / + virtual threads, JDK `HttpClient`, Jackson, SLF4J — defaults the JVM + ecosystem already trusts. +4. **Framework-neutral core.** Core artifacts depend only on the JDK + + Jackson + SLF4J. Spring Boot starter is the adoption surface for the + majority of the audience; Quarkus / Micronaut / Servlet adapters live in + optional artifacts. +5. **Maven Central from day one.** Group `org.adcontextprotocol`, signed + artifacts, reproducible builds, JavaDoc + sources jars. + +## Non-goals + +- **Kotlin DSL as a separate v1.0+ release.** Kotlin co-releases with Java + v1.0 — see [Kotlin positioning](#kotlin-positioning). Spring Boot 3.x is + Kotlin-first in greenfield; "Java interop works" is technically true and + culturally false at Kotlin shops. +- **Scala-native API.** Scala callers consume the Java surface. +- **Android.** The SDK targets server JVMs (publishers, agents, + orchestrators). +- **Reinventing transport.** MCP and A2A bindings wrap upstream Java SDKs the + same way TS wraps `@modelcontextprotocol/sdk` and `@a2a-js/sdk`. + +## Reference: what the SDK has to ship + +From [`docs/building/cross-cutting/sdk-stack.mdx`](https://github.com/adcontextprotocol/adcp/blob/main/docs/building/cross-cutting/sdk-stack.mdx) +("What an SDK at each layer should provide"). Reproduced as a build target: + +| Layer | Server side (agent) | Client side (caller) | +|---|---|---| +| L0 | Generated types from JSON schemas, schema validator, MCP+A2A transport adapters, schema-bundle accessor (build-time + runtime) | Same primitives, mirrored direction | +| L1 | RFC 9421 verification, replay-window enforcement, KMS-pluggable signing for outbound webhooks, verifier test harness | RFC 9421 outbound signing, webhook verification | +| L2 | Account-store abstraction, multi-tenant principal resolution, brand resolution, sandbox/live boundary | Agent-card publication, registry lookup, credential presentation | +| L3 | 7 lifecycle resources tracked + transition validators, idempotency cache (no-payload-echo on conflict, byte-identical replay within TTL), async-task store + dispatcher, webhook emitter, `comply_test_controller` surface, response envelope | State-machine *handlers*, idempotency-key generation, error-recovery classification, async-result polling, webhook receipt | +| L4 | Adopter | Adopter | + +Surface parity target with `@adcp/sdk` 6.x (verified against +`adcontextprotocol/adcp-client` `package.json` exports) — collapsed to +**5 Maven artifacts at GA**, per JVM dependency-hygiene convention: + +| `@adcp/sdk` exports covered | Java artifact | Contents | +|---|---|---| +| root, `/client`, `/types`, `/types/v2-5`, `/auth`, `/advanced`, `/schemas` (resources) | `org.adcontextprotocol:adcp` | Caller, generated types, version co-existence, schema bundle as resources | +| `/server`, `/server/legacy/v5`, `/signing`, `/signing/server`, `/signing/client`, `/express-mcp` (analogue) | `org.adcontextprotocol:adcp-server` | Agent-side primitives, RFC 9421, idempotency, async tasks, webhooks, `comply_test_controller` | +| `/testing`, `/testing/personas`, `/conformance`, `/compliance`, `/compliance-fixtures`, `/substitution`, `/signing/testing`, `/mock-server` | `org.adcontextprotocol:adcp-testing` | Storyboard runner, conformance harness, mock-server forwarding adapter, signing test fixtures | +| (TS framework integration is in-tree; JVM splits it) | `org.adcontextprotocol:adcp-spring-boot-starter` | Auto-configures handler, Jackson, signing, account store, Micrometer, Actuator | +| (TS CLI ships in `bin/adcp.js`) | `org.adcontextprotocol:adcp-cli` | Runnable jar; Homebrew tap as a Java-leads add | + +Schemas ship as resources inside `adcp` (the JVM convention; not a separate +artifact). Quarkus / Micronaut / Servlet adapters land post-v1.0 on adopter +demand. Reactive bridges (`adcp-reactor`, `adcp-mutiny`) ship at GA — see +[Async model](#async-model). + +CLI parity — `adcp [tool] [payload]`, `adcp storyboard run`, +`adcp grade` — ships as a runnable jar + Homebrew tap. **GraalVM +native-image is post-v1.0**, not a v1 commit; reflection config burden plus +moving API surface makes it premature. + +## Architecture + +### Java baseline + +**Java 17 LTS as the compile + runtime baseline.** Java 17 gets us records, +sealed types, pattern matching, text blocks, `HttpClient`. On Java 21+, the +sync-shaped API scales via virtual threads automatically when the adopter +runs on a Loom-backed executor — no capability check, no multi-release JAR. +Document the ergonomics; let the runtime do the work. + +### Type generation + +**Build-time codegen from the published JSON Schemas, emitting Java records +for value/response types and builder records for request types.** Generator +invariant: `*Request` types always have builders; `*Response` types are +records and never do — this naming rule is what makes coding-agent assistance +(Claude / Copilot) work without hallucinating `.builder()` calls on +responses. + +The TS SDK uses `json-schema-to-typescript` plus custom post-processors for +`x-adcp-*` annotations, polymorphic envelopes, and version pinning +(`scripts/generate-types.ts` in `adcontextprotocol/adcp-client`). An +off-the-shelf Java equivalent at that quality doesn't exist — `jsonschema2pojo` +predates records and handles AdCP polymorphism poorly. The Java SDK should +expect to ship a custom generator (probably built on Eclipse JDT or +JavaPoet), but the post-processor scope, not full codegen-from-zero. + +### HTTP transport + +**`java.net.http.HttpClient`** (JDK 11+) for HTTP/1.1 and HTTP/2. No +third-party HTTP client in the core artifact. Optional adapters for OkHttp +and Apache HttpClient 5 are post-v1.0 on demand (mostly to inherit +corporate proxy / mTLS configuration). + +### JSON + +**Jackson 2.x.** It's the de facto JVM JSON library and every Spring / +Quarkus / Micronaut adopter already ships it. Hard depend on +`jackson-databind` **>= 2.15** (the floor for full Java records support); +soft on `jackson-datatype-jsr310` for `Instant`. Document the floor +prominently — Spring Boot 2.7 shops on Jackson 2.13 will hit +`NoSuchMethodError` at runtime, the classic first-hour bounce. + +**Configure `StreamReadConstraints` and `StreamWriteConstraints` explicitly +on the SDK's `ObjectMapper`.** Jackson 2.15+ caps single string values at +20MB and throws `StreamConstraintsException` on over-limit input. Inline +base64 creatives, large catalog responses, and webhook artifacts routinely +exceed that. TS (`JSON.parse`) and Python (`json`) have no equivalent +limits, so cross-language conformance bugs surface here first — a payload +that round-trips through TS and Python will throw on Java unless the +constraints are widened. Ship AdCP-shaped defaults (sized against the +largest spec-permitted creative payload, with headroom), expose adopter +overrides on the client / server builders and via Spring Boot properties +(`adcp.jackson.max-string-length`, etc.), and document the symptom + fix +in the troubleshooting docs so adopters don't burn an afternoon on it. + +### Schema validation + +**`com.networknt:json-schema-validator`** — the actively maintained 2020-12 +JSON Schema validator on the JVM. Lazy-load schemas from the bundled +resources jar; cache validators per schema URI. Same wiring shape as the TS +`SchemaValidator` and Python `adcp.validation`. + +### MCP and A2A + +- **MCP:** wrap the upstream MCP Java SDK. There are multiple in flight + (Spring AI's, the official `io.modelcontextprotocol`); pick one before + v0.1 ships, name it, don't drift between releases. +- **A2A:** the TS SDK wraps `@a2a-js/sdk ^0.3.4` — there's a stable JS + upstream. Java A2A tooling is younger; the anchor is + [`a2aproject/a2a-java`](https://github.com/a2aproject/a2a-java) (the + reference Java implementation under the A2A project). It is pre-1.0 at + the time of writing. **Pre-1.0:** ship a minimal SSE consumer + JSON-RPC + framer in `adcp-server`, depending on `a2a-java` only for type shapes + where stable. **At first `a2a-java` stable tag (≥ 1.0.0 on the + [releases page](https://github.com/a2aproject/a2a-java/releases)):** + switch the transport to the upstream client and deprecate the in-tree + fallback in the next minor. + +This matches how `@adcp/sdk` wraps both `@modelcontextprotocol/sdk` and +`@a2a-js/sdk`. + +### Async model + +The single biggest API-shape decision. Three options: + +| Option | Pros | Cons | +|---|---|---| +| `CompletableFuture` everywhere | JDK-native, no extra deps, integrates with every framework | Verbose chaining, awkward error handling, painful at L3 | +| Reactive in core (Reactor `Mono` / RxJava `Single`) | Powerful composition for WebFlux shops | Reactor is a fight in the dep graph for the median Spring MVC + servlet + blocking-JDBC adopter, who is *not* reactive | +| **Sync + virtual threads (Java 21 Loom)** | Reads like blocking code, scales like async on 21+, no API duplication, maps cleanly to TS `await` and Python `async def`, idiomatic for the median Spring shop | On 17–20 the sync methods block platform threads | + +**Decision: sync-shaped public API everywhere.** The median Spring Boot +adopter is MVC + blocking JDBC, not WebFlux — sync is the *desired* idiom, +not a compromise. WebFlux and Quarkus shops get adapter artifacts: + +- `org.adcontextprotocol:adcp-reactor` — wraps the sync surface in + `Mono.fromCallable(...)` on a bounded elastic scheduler. +- `org.adcontextprotocol:adcp-mutiny` — the Quarkus equivalent. + +**Both ship at GA, not fast-follow.** WebFlux shops left to wrap the sync +API themselves will own that complexity forever and we lose the canonical +surface. + +`CompletableFuture` mirror methods (`*Async`) ship only on the L0 +transport entry points — the ~12 caller methods (`getProducts`, +`createMediaBuy`, `syncCreatives`, `listCreativeFormats`, etc.) where blocking +hurts most for callers stuck on Java 17–20. L2/L3 surfaces stay sync-only. +This bounds the API doubling. + +### Signing (L1) + +`adcp-server` includes signing primitives (no separate `adcp-signing` +artifact at v1 — collapses cleanly into server). Three providers: + +1. **In-process keys** for development — Ed25519 / ECDSA via JCA. **No + Bouncy Castle in the core artifact.** JDK 15+ has Ed25519 natively; the + 17 baseline makes BC unnecessary. Ship `adcp-signing-bouncycastle` as + an optional FIPS-environment provider. +2. **AWS KMS** via `software.amazon.awssdk:kms`. +3. **GCP KMS** via `com.google.cloud:google-cloud-kms`. + +`SigningProvider` is an SPI (`META-INF/services/`). KMS providers wire +**one cryptoKeyVersion per `adcp_use`** — the existing SDK lesson learned +the hard way: don't share keys across `request` / `webhook` / etc. +purposes; receivers enforce purpose at JWK `adcp_use`, not RFC 9421 tag. +Bake this into the API: `SigningProvider.forUse(AdcpUse.WEBHOOK)` returns +a different provider than `forUse(AdcpUse.REQUEST)`. + +KMS init is **lazy**: gRPC retries forever on misconfig, and an eager init +at boot kills deploys silently. Pre-deploy probe is a separate command, +not part of the boot critical path. + +RFC 9421 implementation: hand-roll the canonicalizer (it's small and +spec-tight). Don't depend on `org.tomitribe:http-signatures` — it's the +draft-Cavage spec, not RFC 9421, and quiet since 2022. + +### Server framework integration (L2 + L3) + +The server-side L3 surface is framework-neutral. Adapters glue it to a +hosting framework: + +- `adcp-server` — core. Frameworks plug in via an `AdcpHttpHandler` + interface (request → response). +- `adcp-spring-boot-starter` — **the adoption surface for ~70% of the + audience.** Auto-configures handler, Jackson, signing provider, account + store, **plus Micrometer `MeterRegistry` and Actuator + `HealthIndicator` if those are on the classpath** (auto-published metric + names like `adcp.tool.duration`, `adcp.signing.verify.failures`; + `AdcpHealthIndicator` reports signing-key reachability and account-store + reachability). Spring Security integration is documented as a recipe, + not autoconfig — auth model is too opinionated to assume. + +Quarkus / Micronaut / Servlet adapters land post-v1.0 on demand. The +shape model is the TS SDK's `createAdcpServerFromPlatform` / +`createAdcpServer`: `AdcpServer.builder().platform(myPlatform).build()` +with `platform` being the adopter's L4 implementation. + +### Account store and multi-tenant (L2) + +`AccountStore` interface. Reference impls: + +- `InMemoryAccountStore` for tests. +- `JdbcAccountStore` against a Flyway/Liquibase-managed schema. +- Optional `JpaAccountStore` if Spring Data JPA shops want it. + +Brand-resolution / agent-registry lookup goes through a separate +`RegistryClient` SPI so adopters can point at internal registries instead +of the public AAO registry. + +### Idempotency cache (L3) + +`IdempotencyStore` interface. Reference impls: + +- `InMemoryIdempotencyStore` (tests, single-instance dev). +- `JdbcIdempotencyStore` (production default). +- `RedisIdempotencyStore` via Lettuce (high-throughput). + +Two contracts the type system enforces: + +1. **No-payload-echo on conflict.** `IdempotencyConflict` is a sealed type + that structurally cannot carry a payload echo (read-oracle threat + model — see + [`L1/security.mdx#idempotency`](https://github.com/adcontextprotocol/adcp/blob/main/docs/building/by-layer/L1/security.mdx)). +2. **Byte-identical replay within TTL.** Cached responses replay byte-for-byte; + the store API takes / returns raw bytes alongside the typed response so + replay can't accidentally re-serialize and drift. + +### Lifecycle and transition validation (L3) + +The TS SDK does **not** ship per-resource state machines today +(`adcp-client` `src/lib/server/decisioning/status-changes.ts` says: *"the +framework doesn't validate transitions in v6.0; the field is captured and +projected to the wire so downstream consumers can"*). The 7 resources +(`MediaBuy`, `Creative`, `Account`, `SISession`, `CatalogItem`, `Proposal`, +`Audience`) have spec-defined edges, but no shared YAML lifecycle source +exists yet across SDKs. + +**The Java SDK has two paths:** + +1. **Match TS today.** Capture transitions, project to wire, leave + validation to the caller. Lower scope, lower confidence in + cross-language conformance. +2. **Lead.** Author shared lifecycle YAMLs in the spec repo, wire all + three SDKs (TS, Python, Java) to consume them. Higher scope, but it's + the right shape regardless of who builds it first. + +**Recommend path 2**, owned by the Java build but in coordination with +the TS / Python maintainers. The transition validator API takes +`(action, from, to)` — not `(from, to)` — because spec error-code +precedence depends on the attempted action: `NOT_CANCELLABLE` overrides +`INVALID_STATE` whenever the action is a cancel. A `(from, to)`-only API +loses that distinction. + +`TransitionGuard` SPI lets adopters add preconditions (manual approval +gates, policy checks). Guards run *after* the spec edge check, so they +can never relax a spec edge. **Open risk:** a guard could silently +*narrow* an edge in a way that breaks conformance for buyers expecting +the edge to exist. Mitigation: guards declare which edges they touch; +conformance harness fails if a sandbox account's guards narrow any edge +the storyboards exercise. + +### Async-task store and webhooks (L3) + +`TaskStore` and `WebhookEmitter` SPIs. Same shape as `IdempotencyStore` — +in-memory + JDBC + Redis reference impls. + +**Webhook delivery uses a two-executor pattern, not a single +`ScheduledExecutorService`.** The two are not interchangeable: a scheduler +schedules tasks; it doesn't run blocking work. Splitting them avoids the +trap where an adopter single-threads the scheduler assuming virtual +threads scale it, then wedges their retry pipeline behind one slow +receiver. + +- `WebhookEmitter.scheduler` — a small platform-thread + `ScheduledExecutorService` (default size 1–2). Pure scheduling: pulls + due deliveries off the retry queue and dispatches them. +- `WebhookEmitter.dispatcher` — a separate executor that *runs* the + HTTP delivery. Default on Java 21+: + `Executors.newVirtualThreadPerTaskExecutor()`. Default on 17–20: a + bounded platform-thread pool with explicit size. Configurable + independently of the scheduler. + +Both executors are injectable on `WebhookEmitter.builder()` so adopters +can wire their own (e.g., a Spring `TaskExecutor`, a Mutiny scheduler, a +shared application thread pool). + +The async-task contract has a non-obvious requirement worth surfacing in +the API: **the task's terminal artifact must carry the original tool's +response shape**, not a generic task envelope (sdk-stack.mdx L111). The +`TaskStore.complete(taskId, artifact)` signature is generic in the +artifact type so the compiler enforces this per tool. + +### `comply_test_controller` (L3) + +`adcp-testing` artifact. The same controller surface that `@adcp/sdk` +ships under `/conformance` and `/compliance`, exposed through the same +`seed_*` / `force_*` / `simulate_*` tool names. Sandbox-only enforcement +is wired at the `AccountStore` boundary — production accounts get +`COMPLY_NOT_AVAILABLE` per spec. + +**Mock-server forwarding contract.** Storyboards certify against the +shared reference mock-server, not against an in-process Java mock. The +storyboard runner forwards mock-mode traffic over HTTP to the +mock-server (same shape as TS `/mock-server`). Without this contract, +storyboards run against the SDK's own L4 stub instead of the +spec-compliance oracle, and certification fails. + +### Schema-bundle consumption + +Two paths, both required: + +- **Build-time:** download the protocol tarball + (`https://adcontextprotocol.org/protocol/{version}.tgz`), verify the + Sigstore signature with `cosign verify-blob`, run codegen against the + extracted schemas. Sigstore verification stays at build-time, not + runtime. +- **Runtime:** schema-bundle accessor inside the published artifact for + the validator and version pinning. `AdcpClient.builder().adcpVersion("3.1")` + resolves against bundled resources at construction time. + +### Testing utilities + +`adcp-testing` artifact. JUnit 5 first-class. + +- `AdcpAgentExtension` — JUnit 5 extension that boots an in-process + agent (or wraps an adopter's agent) for storyboard runs. +- `StoryboardRunner` — Java port of the TS `runStoryboard`. Reads the + same YAML storyboards from the protocol bundle, runs them against an + agent under test, asserts wire conformance. +- `MockAgent` — for callers under test, the buyer-side mirror. +- `Personas` — port of `/testing/personas`. + +This closes the storyboard-driven testing story for JVM adopters in the +language of the agent. + +### Cross-cutting + +- **Nullability annotations.** **JSpecify** on every public type. Affects + Kotlin interop and IDE warnings on day one. No `Optional` returns — + Kotlin renders those as `Optional!` and it's ugly; use + `@Nullable T` instead. +- **Module system.** Classpath-only with `Automatic-Module-Name` set in + every JAR's manifest. JPMS modules add friction; opt-in works for the + WildFly / JBoss shops that need them. +- **Logging.** SLF4J facade. No `java.util.logging`, no + `commons-logging` shim. +- **Generator invariant.** `*Request` always builds; `*Response` never + does. Names enforce the invariant; coding-agent assistants stop + hallucinating `.builder()` calls on response types. + +## Spec gotchas a Java implementer should know + +The TS and Python builds bled time on each of these. None are obvious from +the schemas. + +1. **Idempotency cross-payload conflict semantics.** Same key + different + payload → `IDEMPOTENCY_CONFLICT` with **no payload echo** (read-oracle + threat model). Cached-response replay within TTL must be byte-identical. + See [`L1/security.mdx#idempotency`](https://github.com/adcontextprotocol/adcp/blob/main/docs/building/by-layer/L1/security.mdx). +2. **Async terminal-artifact shape.** A task's terminal artifact carries + the *original tool's* response shape, not a generic task envelope. +3. **`VERSION_UNSUPPORTED` is `correctable` and must echo + `supported_versions`.** Get either wrong and the caller can't recover. + See [`version-adaptation.mdx`](https://github.com/adcontextprotocol/adcp/blob/main/docs/building/cross-cutting/version-adaptation.mdx). +4. **Error recovery classification is wire-contract.** + `transient` / `correctable` / `terminal` lives in the spec's + `error-code.json` `enumMetadata` (PR #3738), not SDK-side metadata. The + SDK consumes it, doesn't re-derive it. +5. **`NOT_CANCELLABLE` precedence over `INVALID_STATE`.** Whenever the + attempted action is a cancel, the cancellation-specific code wins. The + transition validator API needs the action, not just the edge. +6. **KMS keys distinct per `adcp_use`.** One key per signing purpose; + receivers enforce purpose at JWK `adcp_use`. Don't share across + request / webhook / etc. +7. **KMS lazy-init.** Eager init at boot can wedge deploys silently + (gRPC retries forever on misconfig). Probe out-of-band. +8. **Storyboard hint fix-plan format.** Conformance failures emit + structured `Diagnose / Locate / Fix / Verify` hints — surface them in + adopter-facing test reports. + +This is the Java SDK's "things we'd tell a new contributor" list, codified +upfront so it's not learned twice. + +## Build, distribution, governance + +- **Build system:** Gradle (better incremental, better multi-module, better + match for codegen pipelines). Maven `pom.xml` published for consumers. +- **Repository:** new `adcontextprotocol/adcp-java` repo, same release + cadence as the other SDKs. +- **Maven Central:** publish via Sonatype OSSRH, GPG-signed. Migrate to + Sigstore for Maven Central once Sonatype's integration GAs. +- **Reproducible builds:** Gradle reproducible-jar + checked-in lockfiles. +- **CI:** GitHub Actions, matrixed across JDK 17 / 21, runs storyboard + conformance against the reference mock-server **every PR from v0.1**. + Same gate TS and Python use. + +### Versioning + +**SDK semver is independent of AdCP spec major.** `@adcp/sdk` is on 6.x +and `adcp` (Python) is on 4.x — both for AdCP 3.x. The TS coincidence of +"6.x for AdCP 3.x" is not a policy, just the artifact of `@adcp/sdk`'s +own pre-3.0 major history. Per +[`docs/reference/versioning`](https://github.com/adcontextprotocol/adcp/blob/main/docs/reference/versioning.mdx) and the +cadence policy (PR #2359), each SDK major supports a defined window of +spec majors via co-existence imports. + +The Java SDK starts at `1.0.0` for v1 GA. Co-existence imports (`adcp-v2-5` +namespace) cover supported spec-version windows. + +## Kotlin positioning + +Co-released with v1.0, not deferred. `adcp-kotlin` is a thin extension +artifact on top of the Java surface: + +- Coroutine extensions (`suspend fun` wrappers) — small, generated. +- DSL builders for request types. +- Nullability already correct (JSpecify across the Java surface, see above). + +Spring Boot 3.x is increasingly Kotlin-first. Defer this and Kotlin shops +hand-roll a wrapper; we lose the canonical surface, and the wrapper +diverges over time. Co-release is a few weeks on top of a clean Java +surface — not a parallel SDK. + +## Migration path + +Four audiences: + +1. **Hand-rolled JVM agents** (a handful of publishers run these today). + Same swap-one-layer-at-a-time path the + [migrate-from-hand-rolled](https://github.com/adcontextprotocol/adcp/blob/main/docs/building/by-layer/L4/migrate-from-hand-rolled.mdx) + doc describes for TS, with JVM-specific entry points. +2. **Python sidecar shops.** Document the "delete the sidecar" path — + typically multi-week, but the win (shared transaction context with + the JVM data layer, no IPC) is large for high-volume agents. +3. **Kotlin/JVM agents on Spring Boot.** What works in v1.0 (Java interop + via `adcp-kotlin`), what's coming (full DSL post-v1.0), pointer to + the Kotlin extension artifact. Without this section Kotlin shops + bounce on "no `suspend fun`" within an hour. +4. **New JVM agents.** Start at L4 against the full-stack SDK. Bulk of + the audience. + +## Open questions + +1. **Async API shape on 17–20.** Sync-shaped + `*Async` mirror on the + ~12 L0 caller methods — confirm the mirror surface size before + v0.1 cut. Do we need `*Async` on `AdcpClient` builder methods too, or + only on the tool-call methods? +2. **MCP Java SDK choice.** Spring AI's vs. official + `io.modelcontextprotocol`. Decide by v0.1 cut; drifting between + releases is worse than picking the less-mature one and migrating. +3. **A2A Java fallback shape.** Anchor is + [`a2aproject/a2a-java`](https://github.com/a2aproject/a2a-java). + Trigger to switch off the in-tree fallback: first `a2a-java` stable + release (≥ 1.0.0 on the + [releases page](https://github.com/a2aproject/a2a-java/releases)). + Open: do we hard-depend on `a2a-java` for type shapes pre-1.0 (locking + us to their package layout churn), or keep types in-tree until the + stable cut and migrate in one shot? Default: in-tree until stable. +4. **Shared lifecycle YAMLs.** Path 2 above (lead the cross-SDK + lifecycle source) requires TS / Python maintainer buy-in. If they + say no, fall back to path 1 and ship transition validators as a + Java-internal shape. +5. **Spring Security integration depth.** Recipes-only vs. a separate + `adcp-spring-boot-starter-security` artifact. Decide based on v0.3 + adopter feedback. +6. **Compatibility with Spring Boot 2.7 / `javax` namespace.** End of OSS + support is Nov 2025, but the painful bit is the **`javax` → `jakarta` + namespace split**: Spring Boot 2.7 is on `javax.servlet` / + `javax.persistence`, 3.x on `jakarta.*`. The starter can't span both + from one artifact without classpath gymnastics. Three shapes: (a) + **floor at 3.x**, accept the long-tail loss — cleanest, recommended; + (b) **ship two starters** (`adcp-spring-boot-starter-2x` on `javax`, + `adcp-spring-boot-starter` on `jakarta`) — doubles the matrix, + mirrors what `springdoc-openapi` does; (c) **defer 2.x to a community + port**. Decide before v0.3 alpha because the starter package layout + depends on it. +7. **TransitionGuard narrowing protection.** Guards declaring the edges + they touch — is that the right enforcement shape, or do we need a + stronger contract? + +## Proposed roadmap + +A target shape, not a public commitment. **Realistic GA is M+12, not +M+9** — pre-committing M+9 and slipping is worse than committing M+12 and +beating it. + +- **v0.1 (M+2):** L0 + storyboard CI gate. Generated types, schema + validation, MCP transport, basic caller. Storyboards green against + mock-server. Maven Central alpha. +- **v0.2 (M+4):** L1 — RFC 9421, KMS providers (lazy-init, per-`adcp_use`), + webhook signing. +- **v0.3 (M+6):** Server-side L2 + partial L3 — account store, + idempotency, async tasks. Spring Boot starter alpha. +- **v0.4 (M+9):** Full L3 — transition validators, webhook emission, + `comply_test_controller`. A2A transport. +- **v1.0 (M+12):** GA. Full L0–L3 parity. `adcp-kotlin` co-released. + Reactor and Mutiny adapters at GA, not later. + +**Slippage risk concentrates on:** MCP Java SDK churn, RFC 9421 +canonicalization edge cases (TS and Python both bled time here), shared +lifecycle YAML coordination, Spring Boot starter scope creep. + +## Decisions wanted + +In priority order: + +1. **Funding / staffing.** The most important decision. Realistic shape: + **a contributed engineer from a member org at 50%+ for ~12 months, + plus a named WG maintainer with merge rights, plus 2 design partners + committed to v0.1–v0.4.** Without all three, decline and revisit at + the next major. Member-funded pooled is theoretically cleaner but + slower to assemble; foundation grant unrealistic on this timeline. +2. **Design partners.** 2–3 JVM shops committing letters of intent to + ship on the Java SDK in 2026. Without them this is build-it-and-they- + will-come; with them it's de-risked. Candidates: a publisher running + on Spring Boot, an SSP, a broadcaster middleware team. +3. **Yes/no on Java as a fourth officially supported language.** Commits + the project to a fourth release per spec rev forever. Real ongoing + cost worth a vote. +4. **Maintainer.** Named owner with merge rights post-GA. This RFC names + no one. +5. **Scope cuts.** Anything in the roadmap above the WG thinks is out of + v1: lifecycle YAML coordination, Reactor/Mutiny at GA, Kotlin + co-release, Spring Boot starter Micrometer/Actuator integration. +6. **Cross-SDK lifecycle YAML.** Owners on TS and Python sides willing + to consume a shared source. Affects path 1 vs. path 2 above. + +## What kills adoption + +Top three failure modes, codified so the build can hedge against them: + +1. **Conformance gap with TS/Python.** If JVM storyboards lag by even + one spec rev, JVM teams won't trust it. Storyboard CI from v0.1 — not + v0.4 — is the structural protection. +2. **Spring Boot starter that's too thin or too opinionated.** Too thin + and adopters write 200 lines of glue and ask why they need the SDK. + Too opinionated and Spring Security / Actuator / Micrometer fights + kill adoption. Wire observability, leave auth. +3. **Async API discord with the framework adopters are on.** + Sync + virtual threads is correct for 21+. WebFlux shops without a + Reactor adapter will wrap the sync API themselves and own that + complexity forever. Reactor + Mutiny adapters at GA, not fast-follow. + +## Appendix: example surface (illustrative) + +Shape only — actual API determined by codegen + WG review. + +### Caller + +```java +import org.adcontextprotocol.adcp.AdcpClient; +import org.adcontextprotocol.adcp.AgentConfig; +import org.adcontextprotocol.adcp.Protocol; +import org.adcontextprotocol.adcp.task.GetProductsRequest; +import org.adcontextprotocol.adcp.task.GetProductsResponse; + +var client = AdcpClient.builder() + .agent(AgentConfig.builder() + .id("sales") + .agentUri("https://sales.example.com/mcp") + .protocol(Protocol.MCP) + .build()) + .build(); + +GetProductsResponse response = client.getProducts( + GetProductsRequest.builder() + .brief("Video campaign for pet owners") + .build()); +``` + +### Agent (Spring Boot) + +```java +@Component +public class MyPlatform implements AdcpPlatform { + @Override + public GetProductsResponse getProducts(GetProductsRequest req, Principal p) { + // L4 — adopter's inventory, pricing, decisioning + } + // ... other tools +} + +// adcp-spring-boot-starter wires: +// - request handler, Jackson, signing provider, account store +// - Micrometer metrics if MeterRegistry present +// - Actuator AdcpHealthIndicator if Actuator present +// Spring Security integration is a documented recipe, not autoconfig. +``` + +### Storyboard test + +```java +@AdcpStoryboardTest(agent = "my-agent") +class ConformanceTest { + @Test + void mediaBuyLifecycle() { + StoryboardRunner.run("media-buy/lifecycle.yaml") + .against(myAgent) + .assertConformant(); + } +} +``` + +## References + +- [SDK stack reference](https://github.com/adcontextprotocol/adcp/blob/main/docs/building/cross-cutting/sdk-stack.mdx) +- [Choose your SDK](https://github.com/adcontextprotocol/adcp/blob/main/docs/building/by-layer/L4/choose-your-sdk.mdx) +- [Schemas](https://github.com/adcontextprotocol/adcp/blob/main/docs/building/by-layer/L0/schemas.mdx) +- [Security model](https://github.com/adcontextprotocol/adcp/blob/main/docs/building/concepts/security-model.mdx) +- [Version adaptation](https://github.com/adcontextprotocol/adcp/blob/main/docs/building/cross-cutting/version-adaptation.mdx) +- [Versioning](https://github.com/adcontextprotocol/adcp/blob/main/docs/reference/versioning.mdx) +- [Conformance](https://github.com/adcontextprotocol/adcp/blob/main/docs/building/verification/conformance.mdx) +- [`L1/security.mdx#idempotency`](https://github.com/adcontextprotocol/adcp/blob/main/docs/building/by-layer/L1/security.mdx) +- `@adcp/sdk` source: github.com/adcontextprotocol/adcp-client +- `adcp` (Python) source: github.com/adcontextprotocol/adcp-client-python +- `adcp-go` source: github.com/adcontextprotocol/adcp-go From a87133e8f02ea139289046553b53b452a3bb5f74 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Wed, 13 May 2026 15:11:22 -0400 Subject: [PATCH 02/12] =?UTF-8?q?Lock=20D6=E2=80=93D9:=20Maven=20cadence,?= =?UTF-8?q?=20jakarta=20floor,=20mock-server=20CI,=20MCP=20SDK?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds four confirmed decisions to the post-RFC table and propagates them through the body: - D6 — first Maven Central publish at v0.3 (not v0.1); Sonatype OSSRH namespace + GPG setup still happens harness Week 1 since approval is the slow path. v0.1 and v0.2 ship local Gradle artifacts only. - D7 — jakarta only / Spring Boot 3.x floor. SB 2.7 is 6 months past OSS EOL; no compat starter, no community 2.7 port. - D8 — mock-server CI sidecar via `npx adcp mock-server`. Pinned @adcp/sdk version becomes the conformance oracle. Confirmed seven specialisms (signal-marketplace, creative-template, sales-guaranteed, sales-non-guaranteed, sales-social, sponsored-intelligence, creative-ad-server). - D9 — io.modelcontextprotocol.sdk:mcp pinned 1.1.2. The Spring AI MCP SDK was donated to the modelcontextprotocol org in Feb 2025 and rebranded as the official Java SDK; current spring-ai-mcp-* artifacts are thin Spring Boot wrappers on top. Two prototype questions remain for harness Week 1: servlet streamable-HTTP transport without Jetty/Tomcat, and Jackson 2 vs. 3 module feature parity. License is MIT — surfaced as a new open question (#9) for the foundation. Resolves RFC Open Questions 2 and 6. Crosses out the resolved items in Decisions Wanted; remaining open: funding/staffing, design partners, WG vote, named maintainer, lifecycle YAML buy-in, Reactor/Mutiny/Kotlin GA scope, MIT-dep position. Co-Authored-By: Claude Opus 4.7 (1M context) --- ROADMAP.md | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 4bd7284..621c1f0 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -14,7 +14,11 @@ Decisions made post-RFC that supersede or refine the merged text. Each will land | D2 | Java baseline | **JDK 21 only.** Drops the 17 floor. Consequences: no `*Async` mirror surface (virtual threads make sync API scale natively), no platform-thread fallback executor in `WebhookEmitter`, `ScopedValue` directly for `UpstreamRecorder` per-principal scope, no Spring Boot 2.7 long-tail support. | RFC §Architecture / Java baseline (was "Java 17 LTS"); RFC §Async model (drops the 12-method `*Async` mirror) | | D3 | Maven coordinates | Group `org.adcontextprotocol`. Artifacts: `adcp` (main), `adcp-server`, `adcp-testing`, `adcp-spring-boot-starter`, `adcp-cli`, `adcp-reactor`, `adcp-mutiny`, `adcp-kotlin`. Base Java package `org.adcontextprotocol.adcp.*`. Sub-packages by surface (e.g. `.task`, `.server`, `.signing`, `.testing`). | RFC §Reference (named artifacts, didn't pin group / base package) | | D4 | Protocol tarball Sigstore | Confirmed signed. Harness schema-fetcher runs `cosign verify-blob` per RFC, no checksum-only fallback path. | Confirms RFC §Schema-bundle consumption assumption | -| D5 | Reference mock-server | The `@adcp/sdk/mock-server` package — same mock-server TS uses. Storyboard CI either runs it as a Node-side-car service container or against a hosted instance. Decide deployment shape during harness Week 1. | Specifies RFC §`comply_test_controller` "shared reference mock-server" | +| D5 | Reference mock-server | The `@adcp/sdk/mock-server` package — same mock-server TS uses. See D8 for CI deployment shape. | Specifies RFC §`comply_test_controller` "shared reference mock-server" | +| D6 | Maven Central publish cadence | **Hold first publish until v0.3 alpha.** v0.1 and v0.2 ship as local Gradle artifacts / SNAPSHOT only. Sonatype OSSRH namespace claim + GPG key setup still happens harness Week 1 (slow path; 1–5 business-day ticket) — we just don't push artifacts until v0.3. | RFC §Build, distribution, governance (RFC said "Maven Central alpha from v0.1") | +| D7 | `javax`/`jakarta` floor | **`jakarta` only**, Spring Boot 3.x floor. Single `adcp-spring-boot-starter` artifact, no compat starter, no community 2.7 port. Spring Boot 2.7 OSS support ended Nov 2025; anyone still on it has a vendor relationship. | Resolves RFC Open Question 6 in favor of option (a) | +| D8 | Mock-server CI deployment | **Sidecar via `npx adcp mock-server`.** GitHub Actions Node step installs a pinned `@adcp/sdk` version, backgrounds one mock-server per specialism on a port range, Java tests hit `localhost`. The pinned `@adcp/sdk` version is the conformance oracle — bumping it is a deliberate PR. Promote to a published Docker image if multi-specialism orchestration becomes unwieldy. | Specifies D5's deployment | +| D9 | MCP Java SDK | **`io.modelcontextprotocol.sdk:mcp` pinned `1.1.2`** at the core. Used by `adcp` (caller) and `adcp-server` (agent). The Spring AI MCP SDK was donated to the `modelcontextprotocol` org in Feb 2025 and rebranded as the official Java SDK; current `spring-ai-mcp-*` artifacts are now thin Spring Boot wrappers on top of `io.modelcontextprotocol.sdk` — no parallel implementation. **License: MIT** (compatible, flagged for foundation position). Two harness Week 1 prototype questions left open: (a) whether `mcp-core`'s servlet-based streamable-HTTP server transport is usable without pulling Jetty/Tomcat; (b) Jackson 2 vs. 3 module split — confirm `mcp-json-jackson2` is feature-equivalent. | Resolves RFC Open Question 2 | ## Parity baseline (as of 2026-05-13) @@ -100,7 +104,8 @@ Hard line: **scaffold the build, leave the rooms empty.** Don't pre-build L1 / L | Gradle multi-module skeleton (5 published artifacts + 2 bridge modules `adcp-reactor` / `adcp-mutiny` as empty stubs) | Stable multi-module graph from day one; package names locked; contributors don't fight `settings.gradle.kts` reviews | [`infra`](#track-1--build-repo-release-infra) | | Schema-bundle fetcher Gradle task: download `{version}.tgz`, `cosign verify-blob`, extract to build dir | Codegen has something to point at; SSRF/signing posture established before any HTTP code lands | [`infra`](#track-1--build-repo-release-infra) | | Codegen MVP: emit records + builder records for **one or two** request/response pairs (e.g. `GetProductsRequest` / `GetProductsResponse`) | Proves the generator architecture, locks in the `*Request`/`*Response` naming invariant, gives contributors real Java to import. Full coverage stays in [`codegen`](#track-2--l0-types--codegen). | [`codegen`](#track-2--l0-types--codegen) | -| MCP SDK decision resolved (Spring AI vs. official `io.modelcontextprotocol`) + recorded in an ADR | Blocks [`transport`](#track-3--l0-transport-mcp--a2a). Drift between releases is worse than picking the less-mature option and migrating. | [`transport`](#track-3--l0-transport-mcp--a2a) | +| Prototype the two open MCP-SDK questions on `io.modelcontextprotocol.sdk:mcp:1.1.2` (per D9): can `mcp-core`'s servlet streamable-HTTP server transport run without Jetty/Tomcat? Is `mcp-json-jackson2` feature-equivalent to the Jackson 3 variant? | D9 picked the SDK; these two are the only unresolved bits before [`transport`](#track-3--l0-transport-mcp--a2a) opens for claim. | [`transport`](#track-3--l0-transport-mcp--a2a) | +| Sonatype OSSRH namespace claim for `org.adcontextprotocol` + foundation GPG key + key-server publication | Slow-path ticket (1–5 business days); start Week 1 even though first publish waits for v0.3 (per D6). Don't block v0.3 on a stalled OSSRH ticket. | [`infra`](#track-1--build-repo-release-infra) | | SSRF-safe `HttpClient` wrapper skeleton (DNS pin, address-guards, redirect:manual, body cap) | Baseline 7.x security posture. JDK `HttpClient` doesn't pin natively; this needs a design doc + skeleton before contributors touch outbound HTTP. | [`transport`](#track-3--l0-transport-mcp--a2a) | | Storyboard CI gate shell: GitHub Actions on JDK 21, runs the runner against the `@adcp/sdk/mock-server`, even if the runner currently asserts only "we reached the server" | The v0.1 release gate is "storyboards green in CI." Standing it up empty and having it pass keeps contributors honest as L0 fills in — every PR is measured against the gate. | [`infra`](#track-1--build-repo-release-infra) + [`testing`](#track-9--testing--conformance) | | Repo conventions: `CONTRIBUTING.md` (track-claim flow), `.github/ISSUE_TEMPLATE/track-claim.md`, PR template, `CLAUDE.md` for agent contributors | The track-claim issue template is the actual contributor onboarding doc | [`docs`](#track-14--docs-migration-troubleshooting) | @@ -145,9 +150,9 @@ Each track entry has: | Milestone | Target | Release gate | |---|---|---| -| v0.1 alpha | M+2 | L0 surface compiles, storyboards green against reference mock-server in CI, Maven Central alpha published | +| v0.1 alpha | M+2 | L0 surface compiles, storyboards green against reference mock-server in CI. Local Gradle artifacts only (per D6 — first Maven Central publish at v0.3). | | v0.2 alpha | M+4 | L1: RFC 9421 signing/verification, AWS+GCP KMS providers (lazy-init, per-`adcp_use`), webhook signing | -| v0.3 alpha | M+6 | L2 + partial L3: account store, idempotency, async tasks, Spring Boot starter alpha | +| v0.3 alpha | M+6 | L2 + partial L3: account store, idempotency, async tasks, Spring Boot starter alpha. **First Maven Central publish** (per D6). | | v0.4 beta | M+9 | Full L3: transition validators, webhook emission, `comply_test_controller`, A2A transport | | v1.0 GA | M+12 | L0–L3 parity, Reactor + Mutiny adapters, Kotlin co-release, Maven Central GA | @@ -166,7 +171,7 @@ The RFC's M+12 target is the realistic line. Pre-committing M+9 and slipping is - `Automatic-Module-Name` set on every JAR manifest. - Gradle reproducible-jar config, lockfiles checked in. - Codegen Gradle task that downloads the protocol tarball, verifies with `cosign verify-blob`, extracts schemas, hands off to [`codegen`](#track-2--l0-types--codegen). -- Maven Central publish via Sonatype OSSRH, GPG-signed; Sigstore migration tracked as a follow-up. +- Sonatype OSSRH namespace + GPG key set up harness Week 1; first Maven Central publish at v0.3 (per D6). Sigstore migration tracked as a follow-up. - GitHub Actions on JDK 21, run storyboard CI against `@adcp/sdk/mock-server` (the v0.1 gate; storyboard runner from [`testing`](#track-9--testing--conformance) plugs in). - JavaDoc + sources jars on every release. @@ -174,7 +179,7 @@ The RFC's M+12 target is the realistic line. Pre-committing M+9 and slipping is **Depends on:** nothing. This track unblocks everything else and should land in week 1. -**Milestone targets:** v0.1 needs Gradle multi-module + Maven Central alpha publish + CI matrix. Sigstore signing migration can land any time before v1.0. +**Milestone targets:** v0.1 needs Gradle multi-module + CI on JDK 21 + green storyboard runner against the sidecar `npx adcp mock-server`. v0.3 adds the first Maven Central publish (per D6). Sigstore signing migration can land any time before v1.0. --- @@ -207,7 +212,7 @@ The RFC's M+12 target is the realistic line. Pre-committing M+9 and slipping is **Scope:** -- **MCP:** pick between Spring AI's MCP SDK and the official `io.modelcontextprotocol` (Open Question 2 in the RFC). Decide before v0.1 cut; drift between releases is worse than picking the less-mature one and migrating later. Wrap the chosen SDK with the AdCP transport surface. +- **MCP:** depend on `io.modelcontextprotocol.sdk:mcp` pinned `1.1.2` (per D9). Used by both `adcp` (caller) and `adcp-server` (agent). Plan a deliberate 2.x migration PR ~6 months out (the 2.0 line removes sealed interfaces from message types, replaces `JsonSchema` with `Map`, flips the tool-input-validation default, removes server-transport builder methods). License is MIT — flagged for foundation position. Two open prototype questions land harness Week 1: whether the servlet-based streamable-HTTP server transport works without pulling Jetty/Tomcat, and whether `mcp-json-jackson2` is feature-equivalent to the Jackson 3 module. - **A2A pre-1.0:** minimal SSE consumer + JSON-RPC framer in `adcp-server`. Default: keep types in-tree until `a2a-java` cuts its first stable release (≥ 1.0.0), then migrate in one shot (RFC Open Question 3). - **A2A post-1.0:** swap transport to `a2aproject/a2a-java`; deprecate the in-tree fallback in the next minor. - HTTP transport on `java.net.http.HttpClient`. No third-party HTTP client in the core. @@ -362,7 +367,7 @@ The RFC's M+12 target is the realistic line. Pre-committing M+9 and slipping is - Actuator `AdcpHealthIndicator` **if on classpath** — reports signing-key reachability + account-store reachability. - Spring properties for tunables: `adcp.jackson.max-string-length`, etc. - Spring Security integration as a **documented recipe**, not autoconfig (RFC §Server framework integration). Decision on `adcp-spring-boot-starter-security` deferred to v0.3 feedback. -- **`javax` vs `jakarta` decision** (RFC Open Question 6): default recommendation is floor at Spring Boot 3.x (`jakarta`). Confirm before v0.3 alpha — starter package layout depends on it. +- `jakarta` only, Spring Boot 3.x floor (per D7). Document the floor in the README so SB 2.7 shops don't burn an hour on the first import. **Out of scope:** Quarkus / Micronaut / Servlet adapters (post-v1.0 on demand). @@ -475,17 +480,21 @@ A single contributor can claim multiple non-conflicting tracks (e.g. `cli` + `do ## Decisions wanted (blockers before scaling contributor count) -In priority order, from the RFC: +In priority order, from the RFC. Items marked **DONE** are locked in [Confirmed decisions](#confirmed-decisions); kept here as numbered references so RFC readers can map across. 1. **Funding / staffing.** A contributed engineer at 50%+ for ~12 months, plus a named WG maintainer with merge rights, plus 2–3 design partners committed v0.1 → v0.4. Without all three, the RFC says decline and revisit at next major. 2. **Design partners.** 2–3 JVM shops with letters of intent to ship on the SDK in 2026. 3. **WG vote** on Java as the fourth officially supported language. 4. **Maintainer.** Named owner with merge rights post-GA. -5. **MCP Java SDK choice.** Spring AI vs. official `io.modelcontextprotocol`. Blocks `transport`. +5. ~~MCP Java SDK choice.~~ **DONE** — D9. 6. **Cross-SDK lifecycle YAML buy-in.** TS + Python maintainers willing to consume a shared source. Decides `lifecycle` path 1 vs. path 2. -7. **`javax` vs `jakarta` floor.** Decides `spring` artifact layout. +7. ~~`javax` vs `jakarta` floor.~~ **DONE** — D7. 8. **Reactor/Mutiny + Kotlin at GA vs. fast-follow** — RFC's position is "at GA"; WG can cut scope. +Additional decisions added post-RFC that remain open: + +9. **MIT-licensed dependency position.** D9 picked the MIT-licensed `io.modelcontextprotocol.sdk`. License is compatible with Apache 2.0 downstream use, but the foundation may want an explicit position on accepting MIT deps in officially supported SDKs. + ## What's not in this plan (yet) - Hiring or sourcing the contributed engineer / design partners — that's a foundation-level conversation, not a planning artifact. From 967ae6a9c1ae414c9ffba8d3baa956c1872aa77b Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Wed, 13 May 2026 15:23:45 -0400 Subject: [PATCH 03/12] =?UTF-8?q?Lock=20D10=E2=80=93D21:=20A2A,=20guard,?= =?UTF-8?q?=20security,=20async=20GA,=20conventions,=20IPR,=20infra?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds twelve more confirmed decisions to the post-RFC table. Protocol / architecture: - D10 A2A pre-1.0 types: in-tree until a2aproject/a2a-java cuts ≥1.0.0 (currently 1.0.0.Beta1 — pre-stable, package layout still churning). - D11 TransitionGuard: guards declare touched edges; conformance harness fails if a sandbox account's guards narrow a storyboard edge. - D12 Spring Security: recipes-only at v1.0; no separate -security starter. Revisit on v0.3 feedback. - D13 Reactor + Mutiny adapters at GA, not fast-follow. - D14 Kotlin co-release at v1.0 as a thin extension. - D15 Spec-rev tracking: ≤2 weeks from spec rev to Java release. Repo conventions (matched to existing AdCP family practice): - D16 Design decisions filed in specs/.md, not docs/adr/. The Java SDK RFC itself lives at specs/java-sdk-rfc.md in the adcp repo; match that convention. ROADMAP table is the at-a-glance index; specs/ doc only when the row isn't enough. - D17 Trunk-based branching; semver tags from main. - D18 Conventional Commits + commitlint + Changesets, matching TS SDK. - D19 AAO IPR Bot pattern, not DCO. Replicates the same flow installed on adcp / adcp-client / adcp-client-python / adcp-go: contributors comment "I have read the IPR Policy" on first PR, App enforces via required status check. Harness Week 1 installs the bot on this repo and adds the ~15-line caller workflow. Infra: - D20 Sonatype OSSRH namespace claim for org.adcontextprotocol via DNS TXT verification (foundation adds the record). Maven Central confirms the namespace is currently unclaimed (zero artifacts). - D21 Branch protection on main: 1 code-owner review, required CI checks including IPR Policy / Signature, no force-push. Harness §Pre-contributor table updated to drop the ADR-directory item (superseded by D16) and add IPR caller workflow, DNS-TXT, branch protection, and commitlint/Changesets items. Decisions wanted reduced to the three foundation-owned + two cross-team items plus the MIT-dep position. Co-Authored-By: Claude Opus 4.7 (1M context) --- ROADMAP.md | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 621c1f0..5aad254 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -6,7 +6,7 @@ This is a living document. Status updates land here; design decisions land in th ## Confirmed decisions -Decisions made post-RFC that supersede or refine the merged text. Each will land as a numbered ADR under `docs/adr/` during the harness phase. +Decisions made post-RFC that supersede or refine the merged text. The table below is the index. When a row needs more space to explain, it gets its own `specs/.md` (per D16) — by default the table row is the spec. | # | Decision | Resolution | Supersedes RFC | |---|---|---|---| @@ -19,6 +19,18 @@ Decisions made post-RFC that supersede or refine the merged text. Each will land | D7 | `javax`/`jakarta` floor | **`jakarta` only**, Spring Boot 3.x floor. Single `adcp-spring-boot-starter` artifact, no compat starter, no community 2.7 port. Spring Boot 2.7 OSS support ended Nov 2025; anyone still on it has a vendor relationship. | Resolves RFC Open Question 6 in favor of option (a) | | D8 | Mock-server CI deployment | **Sidecar via `npx adcp mock-server`.** GitHub Actions Node step installs a pinned `@adcp/sdk` version, backgrounds one mock-server per specialism on a port range, Java tests hit `localhost`. The pinned `@adcp/sdk` version is the conformance oracle — bumping it is a deliberate PR. Promote to a published Docker image if multi-specialism orchestration becomes unwieldy. | Specifies D5's deployment | | D9 | MCP Java SDK | **`io.modelcontextprotocol.sdk:mcp` pinned `1.1.2`** at the core. Used by `adcp` (caller) and `adcp-server` (agent). The Spring AI MCP SDK was donated to the `modelcontextprotocol` org in Feb 2025 and rebranded as the official Java SDK; current `spring-ai-mcp-*` artifacts are now thin Spring Boot wrappers on top of `io.modelcontextprotocol.sdk` — no parallel implementation. **License: MIT** (compatible, flagged for foundation position). Two harness Week 1 prototype questions left open: (a) whether `mcp-core`'s servlet-based streamable-HTTP server transport is usable without pulling Jetty/Tomcat; (b) Jackson 2 vs. 3 module split — confirm `mcp-json-jackson2` is feature-equivalent. | Resolves RFC Open Question 2 | +| D10 | A2A pre-1.0 type strategy | **Keep A2A types in-tree until `a2aproject/a2a-java` cuts a stable ≥ 1.0.0 release**, then migrate to the upstream client in one shot and deprecate the in-tree fallback in the next minor. As of the latest check, `a2a-java` is at `1.0.0.Beta1` (Apr 2026) — package layout still churning, so we don't hard-depend on it yet. | RFC default for Open Question 3 | +| D11 | `TransitionGuard` narrowing protection | **Guards declare which spec edges they touch.** Conformance harness fails if a sandbox account's guards narrow any edge the storyboards exercise. Guards run after the spec edge check and can never relax a spec edge. | Resolves RFC Open Question 7 | +| D12 | Spring Security integration depth | **Recipes-only at v1.0.** No separate `adcp-spring-boot-starter-security` artifact. Auth models vary too much to pre-bake; recipes age better than autoconfig. Revisit if v0.3 design-partner feedback demands it. | RFC default for Open Question 5 | +| D13 | Reactor + Mutiny adapters | **At GA, not fast-follow.** `adcp-reactor` and `adcp-mutiny` both ship in v1.0. WebFlux shops left to wrap the sync API would own that complexity forever and we'd lose the canonical surface. | Confirms RFC §Async model | +| D14 | Kotlin co-release | **At v1.0, thin extension artifact** (`adcp-kotlin`). Coroutine `suspend fun` wrappers + DSL builders. Not a parallel SDK. Defer and Kotlin shops fork. | Confirms RFC §Kotlin positioning | +| D15 | Spec-rev tracking cadence | **≤ 2 weeks from AdCP spec rev to a Java SDK release that consumes it.** Same SLO TS and Python hold to. Slower than 2 weeks and JVM teams stop trusting the parity claim. | Specifies RFC §What kills adoption (item 1) | +| D16 | Design-decision filing convention | **Match AdCP convention.** Longer-form design / decision docs live in `specs/.md` (the same place the Java SDK RFC itself lives in the spec repo). The Confirmed-decisions table in this ROADMAP is the at-a-glance index; a decision spins up its own `specs/` doc only when the table row isn't enough. No `docs/adr/`, no MADR template, no per-decision file by default. | (No RFC §; sets a repo convention) | +| D17 | Branching model | **Trunk-based.** Short-lived feature branches → PR to `main`. Semver tags from `main`. No long-lived release branches. | (Sets a repo convention; matches TS SDK) | +| D18 | Commits + changelog | **Conventional Commits enforced via commitlint; [Changesets](https://github.com/changesets/changesets) for the CHANGELOG.** Matches the TS SDK's workflow shape (same `.changeset/` directory pattern) so humans and tools read both SDKs' release notes the same way. | (Sets a repo convention; matches TS SDK) | +| D19 | Contributor IPR | **Replicate the AAO IPR Bot pattern** used by adcp / adcp-client / adcp-client-python / adcp-go. Contributors agree by commenting `I have read the IPR Policy` on their first PR; the AAO IPR Bot (a GitHub App) enforces via a required status check. Harness Week 1 actions: (1) foundation admin adds `adcontextprotocol/adcp-sdk-java` to the App's installation scope and the `IPR_APP_ID` / `IPR_APP_PRIVATE_KEY` org-secret scope; (2) add the ~15-line caller workflow at `.github/workflows/ipr.yml` invoking `adcontextprotocol/adcp/.github/workflows/ipr-check-callable.yml@main`; (3) `CONTRIBUTING.md` mirrors the adcp wording and links to `IPR_POLICY.md` in the spec repo. **No DCO. No CLA.** | (Matches AAO standard) | +| D20 | Sonatype OSSRH namespace claim | **Claim `org.adcontextprotocol` via DNS TXT verification.** Maven Central confirms the namespace is unclaimed (zero artifacts today). Requires the foundation to add one `TXT` record to `adcontextprotocol.org` proving control. Sonatype ticket + DNS record both kicked off harness Week 1; first publish waits for v0.3 (per D6). | Specifies D6 | +| D21 | Branch protection on `main` | **Required: 1 code-owner approving review, required CI checks (`build`, `test`, `storyboard`, `IPR Policy / Signature`), no force-push, no direct push, no admin bypass.** Dependabot patch PRs auto-merge after green CI. Two-reviewer gate doesn't add safety with a founder pair; revisit when contributor base grows. | (Sets a repo convention) | ## Parity baseline (as of 2026-05-13) @@ -108,8 +120,11 @@ Hard line: **scaffold the build, leave the rooms empty.** Don't pre-build L1 / L | Sonatype OSSRH namespace claim for `org.adcontextprotocol` + foundation GPG key + key-server publication | Slow-path ticket (1–5 business days); start Week 1 even though first publish waits for v0.3 (per D6). Don't block v0.3 on a stalled OSSRH ticket. | [`infra`](#track-1--build-repo-release-infra) | | SSRF-safe `HttpClient` wrapper skeleton (DNS pin, address-guards, redirect:manual, body cap) | Baseline 7.x security posture. JDK `HttpClient` doesn't pin natively; this needs a design doc + skeleton before contributors touch outbound HTTP. | [`transport`](#track-3--l0-transport-mcp--a2a) | | Storyboard CI gate shell: GitHub Actions on JDK 21, runs the runner against the `@adcp/sdk/mock-server`, even if the runner currently asserts only "we reached the server" | The v0.1 release gate is "storyboards green in CI." Standing it up empty and having it pass keeps contributors honest as L0 fills in — every PR is measured against the gate. | [`infra`](#track-1--build-repo-release-infra) + [`testing`](#track-9--testing--conformance) | -| Repo conventions: `CONTRIBUTING.md` (track-claim flow), `.github/ISSUE_TEMPLATE/track-claim.md`, PR template, `CLAUDE.md` for agent contributors | The track-claim issue template is the actual contributor onboarding doc | [`docs`](#track-14--docs-migration-troubleshooting) | -| ADR directory + first three ADRs (Java baseline, async model, MCP SDK choice) | RFC decisions captured in the format contributors expect to amend | [`docs`](#track-14--docs-migration-troubleshooting) | +| Repo conventions: `CONTRIBUTING.md` (track-claim flow + IPR pointer per D19), `.github/ISSUE_TEMPLATE/track-claim.md`, PR template, `CLAUDE.md` for agent contributors | The track-claim issue template is the actual contributor onboarding doc | [`docs`](#track-14--docs-migration-troubleshooting) | +| AAO IPR caller workflow at `.github/workflows/ipr.yml` (per D19) + foundation admin installs the IPR Bot on this repo + adds it to the `IPR_APP_ID` / `IPR_APP_PRIVATE_KEY` org-secret scope | Required-status check `IPR Policy / Signature` is the gate on every PR (per D21) — must be working before contributor PRs arrive | [`docs`](#track-14--docs-migration-troubleshooting) + [`infra`](#track-1--build-repo-release-infra) | +| DNS TXT record on `adcontextprotocol.org` for Sonatype namespace verification (per D20) | Foundation-admin action; pairs with the OSSRH ticket. Without it the ticket stalls. | [`infra`](#track-1--build-repo-release-infra) | +| Branch protection on `main` per D21 (required reviews, required checks, no force-push, no admin bypass) | Locks the trunk-based model (D17) before tracks open | [`infra`](#track-1--build-repo-release-infra) | +| Commitlint + Changesets wiring (per D18) | Adopter expectations are set by `@adcp/sdk`'s release notes; matching the workflow shape means humans and tools read both SDKs' changelogs the same way | [`infra`](#track-1--build-repo-release-infra) + [`docs`](#track-14--docs-migration-troubleshooting) | ### Explicitly **not** in the harness @@ -489,11 +504,14 @@ In priority order, from the RFC. Items marked **DONE** are locked in [Confirmed 5. ~~MCP Java SDK choice.~~ **DONE** — D9. 6. **Cross-SDK lifecycle YAML buy-in.** TS + Python maintainers willing to consume a shared source. Decides `lifecycle` path 1 vs. path 2. 7. ~~`javax` vs `jakarta` floor.~~ **DONE** — D7. -8. **Reactor/Mutiny + Kotlin at GA vs. fast-follow** — RFC's position is "at GA"; WG can cut scope. +8. ~~Reactor/Mutiny + Kotlin at GA vs. fast-follow.~~ **DONE** — D13 (Reactor + Mutiny at GA) and D14 (Kotlin at GA, thin extension). Additional decisions added post-RFC that remain open: 9. **MIT-licensed dependency position.** D9 picked the MIT-licensed `io.modelcontextprotocol.sdk`. License is compatible with Apache 2.0 downstream use, but the foundation may want an explicit position on accepting MIT deps in officially supported SDKs. +10. **Funding model shape.** RFC framing (contributed engineer at 50%+ for ~12 months + named maintainer + 2–3 design partners) is the right ask; whether it's pooled member funding, single-anchor-org contribution, or foundation grant is open. +11. **Design partner outreach.** Anchor candidates by audience segment: one publisher running Spring Boot, one SSP, one broadcaster middleware team. Specific shops TBD. +12. **WG vote timing.** Recommendation: hold the vote at v0.1 alpha milestone (concrete working code) rather than now (abstract commitment). ## What's not in this plan (yet) From cdf1c2f3161391050634c97255a453cdd7e7f60a Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Wed, 13 May 2026 16:30:27 -0400 Subject: [PATCH 04/12] Harness: Gradle 9.5 multi-module skeleton + CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First piece of harness Week 1 work. Eight published artifacts wired per D3 with the build graph in place; convention plugins centralize the JDK-21 toolchain (D2) and shared compile/test config. Modules (all building, all stamping Automatic-Module-Name): - adcp main library: caller, types, schemas - adcp-server agent-side: signing, idempotency, async - adcp-testing storyboard runner, JUnit 5 fixtures - adcp-spring-boot-starter Spring Boot 3.x autoconfig (D7 jakarta) - adcp-cli runnable jar (placeholder main today) - adcp-reactor Project Reactor adapter (D13) - adcp-mutiny SmallRye Mutiny adapter (D13) - adcp-kotlin Kotlin coroutine + DSL extensions (D14) Build-logic: - adcp.java-base-conventions JDK 21 toolchain, JSpecify on the compile classpath, manifest stamping, lint+Werror (minus -processing/-options to tolerate package-info-only skeleton modules) - adcp.java-library-conventions library plugin + JUnit 5 test wiring - adcp.kotlin-library-conventions Kotlin variant for adcp-kotlin Version catalog (gradle/libs.versions.toml) pins: - io.modelcontextprotocol.sdk:mcp 1.1.2 (D9) - Jackson 2.18.2 (RFC §JSON — >=2.15 floor for records support) - com.networknt:json-schema-validator 1.5.6 - SLF4J 2.0.16, JSpecify 1.0.0, JUnit 5.11.4 - Spring Boot 3.4.4 (D7 — 3.x / jakarta only) - Reactor 3.7.2, Mutiny 2.7.0, Kotlin 2.1.10, kotlinx-coroutines 1.10.1 Verified locally: - `./gradlew build` green across all 8 modules - JAR manifests show Automatic-Module-Name = org.adcontextprotocol. - `./gradlew :adcp-cli:run` prints the placeholder usage stub - All modules produce main + sources + javadoc JARs CI (.github/workflows/ci.yml): - JDK 21 Temurin via actions/setup-java - gradle/actions/setup-gradle with validate-wrappers (supply-chain guard against tampered gradle-wrapper.jar) - Runs ./gradlew build on every PR and push to main This is the foundation everything else builds on. Next harness items land on top: schema-bundle fetcher, codegen MVP, SSRF-safe HttpClient wrapper, storyboard CI gate with @adcp/sdk/mock-server sidecar (D8). Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 32 +++ .gitignore | 25 +- adcp-cli/build.gradle.kts | 25 ++ .../org/adcontextprotocol/adcp/cli/Main.java | 17 ++ adcp-kotlin/build.gradle.kts | 14 + .../org/adcontextprotocol/adcp/kotlin/Adcp.kt | 5 + adcp-mutiny/build.gradle.kts | 13 + .../adcp/mutiny/package-info.java | 6 + adcp-reactor/build.gradle.kts | 13 + .../adcp/reactor/package-info.java | 8 + adcp-server/build.gradle.kts | 18 ++ .../adcp/server/package-info.java | 8 + adcp-spring-boot-starter/build.gradle.kts | 16 ++ .../adcp/springboot/package-info.java | 9 + adcp-testing/build.gradle.kts | 19 ++ .../adcp/testing/package-info.java | 8 + adcp/build.gradle.kts | 17 ++ .../adcontextprotocol/adcp/package-info.java | 9 + build-logic/build.gradle.kts | 15 ++ build-logic/settings.gradle.kts | 13 + .../adcp.java-base-conventions.gradle.kts | 66 +++++ .../adcp.java-library-conventions.gradle.kts | 13 + ...adcp.kotlin-library-conventions.gradle.kts | 17 ++ build.gradle.kts | 7 + gradle.properties | 4 + gradle/libs.versions.toml | 75 ++++++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 48462 bytes gradle/wrapper/gradle-wrapper.properties | 9 + gradlew | 248 ++++++++++++++++++ gradlew.bat | 82 ++++++ settings.gradle.kts | 27 ++ 31 files changed, 835 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 adcp-cli/build.gradle.kts create mode 100644 adcp-cli/src/main/java/org/adcontextprotocol/adcp/cli/Main.java create mode 100644 adcp-kotlin/build.gradle.kts create mode 100644 adcp-kotlin/src/main/kotlin/org/adcontextprotocol/adcp/kotlin/Adcp.kt create mode 100644 adcp-mutiny/build.gradle.kts create mode 100644 adcp-mutiny/src/main/java/org/adcontextprotocol/adcp/mutiny/package-info.java create mode 100644 adcp-reactor/build.gradle.kts create mode 100644 adcp-reactor/src/main/java/org/adcontextprotocol/adcp/reactor/package-info.java create mode 100644 adcp-server/build.gradle.kts create mode 100644 adcp-server/src/main/java/org/adcontextprotocol/adcp/server/package-info.java create mode 100644 adcp-spring-boot-starter/build.gradle.kts create mode 100644 adcp-spring-boot-starter/src/main/java/org/adcontextprotocol/adcp/springboot/package-info.java create mode 100644 adcp-testing/build.gradle.kts create mode 100644 adcp-testing/src/main/java/org/adcontextprotocol/adcp/testing/package-info.java create mode 100644 adcp/build.gradle.kts create mode 100644 adcp/src/main/java/org/adcontextprotocol/adcp/package-info.java create mode 100644 build-logic/build.gradle.kts create mode 100644 build-logic/settings.gradle.kts create mode 100644 build-logic/src/main/kotlin/adcp.java-base-conventions.gradle.kts create mode 100644 build-logic/src/main/kotlin/adcp.java-library-conventions.gradle.kts create mode 100644 build-logic/src/main/kotlin/adcp.kotlin-library-conventions.gradle.kts create mode 100644 build.gradle.kts create mode 100644 gradle.properties create mode 100644 gradle/libs.versions.toml create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle.kts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..0dda1c5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,32 @@ +name: build + +on: + pull_request: + push: + branches: [main] + +permissions: + contents: read + +jobs: + build: + name: ./gradlew build (JDK 21) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 + with: + # Wrapper-validation guards against a tampered gradle-wrapper.jar + # — the standard supply-chain check for OSS Java repos. + validate-wrappers: true + + - name: Build all modules + run: ./gradlew build --stacktrace diff --git a/.gitignore b/.gitignore index 524f096..2a62b84 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,19 @@ +# Gradle +.gradle/ +build/ +local.properties + +# IDE +.idea/ +*.iml +.vscode/ +.classpath +.project +.settings/ + +# OS +.DS_Store + # Compiled class file *.class @@ -10,8 +26,7 @@ # Mobile Tools for Java (J2ME) .mtj.tmp/ -# Package Files # -*.jar +# Package Files *.war *.nar *.ear @@ -19,6 +34,10 @@ *.tar.gz *.rar -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +# JARs — except the Gradle wrapper. +*.jar +!gradle/wrapper/gradle-wrapper.jar + +# JVM crash logs hs_err_pid* replay_pid* diff --git a/adcp-cli/build.gradle.kts b/adcp-cli/build.gradle.kts new file mode 100644 index 0000000..c7651c8 --- /dev/null +++ b/adcp-cli/build.gradle.kts @@ -0,0 +1,25 @@ +// adcp-cli — runnable jar. Commands: `adcp [tool] [payload]`, +// `adcp storyboard run`, `adcp grade`, KMS pre-deploy probe (per RFC). +// Homebrew tap is a Java-leads add (post-v0.4). + +plugins { + id("adcp.java-base-conventions") + application +} + +description = "AdCP Java SDK — CLI" + +dependencies { + implementation(project(":adcp")) + implementation(project(":adcp-testing")) + runtimeOnly(libs.slf4j.simple) + + testImplementation(libs.junit.jupiter.api) + testRuntimeOnly(libs.junit.jupiter.engine) + testRuntimeOnly(libs.junit.platform.launcher) +} + +application { + mainClass = "org.adcontextprotocol.adcp.cli.Main" + applicationName = "adcp" +} diff --git a/adcp-cli/src/main/java/org/adcontextprotocol/adcp/cli/Main.java b/adcp-cli/src/main/java/org/adcontextprotocol/adcp/cli/Main.java new file mode 100644 index 0000000..570d918 --- /dev/null +++ b/adcp-cli/src/main/java/org/adcontextprotocol/adcp/cli/Main.java @@ -0,0 +1,17 @@ +package org.adcontextprotocol.adcp.cli; + +/** + * Entry point for the {@code adcp} CLI. Commands land here as the CLI track + * is implemented; today this is a placeholder that prints a usage stub. + */ +public final class Main { + + private Main() { + throw new AssertionError("no instances"); + } + + public static void main(String[] args) { + System.out.println("adcp "); + System.out.println("see ROADMAP.md track 13 (cli) for the planned surface"); + } +} diff --git a/adcp-kotlin/build.gradle.kts b/adcp-kotlin/build.gradle.kts new file mode 100644 index 0000000..b416f00 --- /dev/null +++ b/adcp-kotlin/build.gradle.kts @@ -0,0 +1,14 @@ +// adcp-kotlin — Kotlin extensions on top of the Java surface (D14 — at v1.0). +// Coroutine suspend-fun wrappers, DSL builders. Nullability already correct via +// JSpecify on the Java surface. + +plugins { + id("adcp.kotlin-library-conventions") +} + +description = "AdCP Java SDK — Kotlin coroutine + DSL extensions" + +dependencies { + api(project(":adcp")) + api(libs.kotlinx.coroutines.core) +} diff --git a/adcp-kotlin/src/main/kotlin/org/adcontextprotocol/adcp/kotlin/Adcp.kt b/adcp-kotlin/src/main/kotlin/org/adcontextprotocol/adcp/kotlin/Adcp.kt new file mode 100644 index 0000000..d418f43 --- /dev/null +++ b/adcp-kotlin/src/main/kotlin/org/adcontextprotocol/adcp/kotlin/Adcp.kt @@ -0,0 +1,5 @@ +// Kotlin extensions on top of the Java AdCP surface. Coroutine suspend-fun +// wrappers and DSL builders land here as the Java public surface stabilizes. +// Nullability is correct because the Java surface is JSpecify-annotated. + +package org.adcontextprotocol.adcp.kotlin diff --git a/adcp-mutiny/build.gradle.kts b/adcp-mutiny/build.gradle.kts new file mode 100644 index 0000000..ab27447 --- /dev/null +++ b/adcp-mutiny/build.gradle.kts @@ -0,0 +1,13 @@ +// adcp-mutiny — SmallRye Mutiny bridge over the sync surface (D13 — at GA). +// Quarkus equivalent of adcp-reactor. + +plugins { + id("adcp.java-library-conventions") +} + +description = "AdCP Java SDK — SmallRye Mutiny adapter" + +dependencies { + api(project(":adcp")) + api(libs.mutiny) +} diff --git a/adcp-mutiny/src/main/java/org/adcontextprotocol/adcp/mutiny/package-info.java b/adcp-mutiny/src/main/java/org/adcontextprotocol/adcp/mutiny/package-info.java new file mode 100644 index 0000000..f9b9a7e --- /dev/null +++ b/adcp-mutiny/src/main/java/org/adcontextprotocol/adcp/mutiny/package-info.java @@ -0,0 +1,6 @@ +/** + * SmallRye Mutiny bridge — Quarkus equivalent of {@code adcp-reactor}. + * Ships at GA. + */ +@org.jspecify.annotations.NullMarked +package org.adcontextprotocol.adcp.mutiny; diff --git a/adcp-reactor/build.gradle.kts b/adcp-reactor/build.gradle.kts new file mode 100644 index 0000000..6d74d62 --- /dev/null +++ b/adcp-reactor/build.gradle.kts @@ -0,0 +1,13 @@ +// adcp-reactor — Project Reactor bridge over the sync surface (D13 — at GA). +// Wraps blocking calls in Mono.fromCallable on a bounded elastic scheduler. + +plugins { + id("adcp.java-library-conventions") +} + +description = "AdCP Java SDK — Project Reactor adapter" + +dependencies { + api(project(":adcp")) + api(libs.reactor.core) +} diff --git a/adcp-reactor/src/main/java/org/adcontextprotocol/adcp/reactor/package-info.java b/adcp-reactor/src/main/java/org/adcontextprotocol/adcp/reactor/package-info.java new file mode 100644 index 0000000..d003550 --- /dev/null +++ b/adcp-reactor/src/main/java/org/adcontextprotocol/adcp/reactor/package-info.java @@ -0,0 +1,8 @@ +/** + * Project Reactor bridge — wraps the synchronous {@code adcp} surface in + * {@code Mono.fromCallable(...)} on a bounded elastic scheduler. Ships at + * GA (not fast-follow) so WebFlux shops don't wrap the sync API + * themselves and own that complexity forever. + */ +@org.jspecify.annotations.NullMarked +package org.adcontextprotocol.adcp.reactor; diff --git a/adcp-server/build.gradle.kts b/adcp-server/build.gradle.kts new file mode 100644 index 0000000..3215e9d --- /dev/null +++ b/adcp-server/build.gradle.kts @@ -0,0 +1,18 @@ +// adcp-server — agent-side primitives, RFC 9421, idempotency, async tasks, +// webhooks, comply_test_controller, upstream-recorder (7.x addition, D9-adjacent). +// Per RFC §Reference: covers TS exports `/server`, `/server/legacy/v5`, +// `/signing`, `/signing/server`, `/signing/client`, `/express-mcp` analogue. + +plugins { + id("adcp.java-library-conventions") +} + +description = "AdCP Java SDK — server-side primitives, signing, async tasks, webhooks" + +dependencies { + api(project(":adcp")) + api(libs.mcp) + // jakarta.servlet-api is pulled in by mcp-core's HTTP server transport. + // D9 R1 prototype question: can this run without Jetty/Tomcat? + compileOnly(libs.jakarta.servlet.api) +} diff --git a/adcp-server/src/main/java/org/adcontextprotocol/adcp/server/package-info.java b/adcp-server/src/main/java/org/adcontextprotocol/adcp/server/package-info.java new file mode 100644 index 0000000..ac93144 --- /dev/null +++ b/adcp-server/src/main/java/org/adcontextprotocol/adcp/server/package-info.java @@ -0,0 +1,8 @@ +/** + * AdCP server-side primitives — request handler, RFC 9421 signing, + * idempotency cache, async-task store, webhook emitter, + * {@code comply_test_controller}, and the upstream-recorder middleware + * (added in {@code @adcp/sdk} 7.x). + */ +@org.jspecify.annotations.NullMarked +package org.adcontextprotocol.adcp.server; diff --git a/adcp-spring-boot-starter/build.gradle.kts b/adcp-spring-boot-starter/build.gradle.kts new file mode 100644 index 0000000..d0866ba --- /dev/null +++ b/adcp-spring-boot-starter/build.gradle.kts @@ -0,0 +1,16 @@ +// adcp-spring-boot-starter — Spring Boot 3.x autoconfig per D7 (jakarta only). +// Auto-configures: handler, Jackson, signing provider, account store, +// Micrometer (if classpath), Actuator (if classpath). Spring Security is a +// documented recipe per D12, not autoconfig. + +plugins { + id("adcp.java-library-conventions") +} + +description = "AdCP Java SDK — Spring Boot starter (Spring Boot 3.x / jakarta only)" + +dependencies { + api(project(":adcp-server")) + api(libs.spring.boot.autoconfigure) + annotationProcessor(libs.spring.boot.configuration.processor) +} diff --git a/adcp-spring-boot-starter/src/main/java/org/adcontextprotocol/adcp/springboot/package-info.java b/adcp-spring-boot-starter/src/main/java/org/adcontextprotocol/adcp/springboot/package-info.java new file mode 100644 index 0000000..e6dc788 --- /dev/null +++ b/adcp-spring-boot-starter/src/main/java/org/adcontextprotocol/adcp/springboot/package-info.java @@ -0,0 +1,9 @@ +/** + * Spring Boot 3.x autoconfiguration for AdCP. Wires the request handler, + * Jackson {@code ObjectMapper}, signing provider, account store, plus + * Micrometer {@code MeterRegistry} and Actuator {@code HealthIndicator} + * when those are on the classpath. Spring Security integration is a + * documented recipe, not autoconfig. + */ +@org.jspecify.annotations.NullMarked +package org.adcontextprotocol.adcp.springboot; diff --git a/adcp-testing/build.gradle.kts b/adcp-testing/build.gradle.kts new file mode 100644 index 0000000..678f1d3 --- /dev/null +++ b/adcp-testing/build.gradle.kts @@ -0,0 +1,19 @@ +// adcp-testing — storyboard runner, conformance harness, mock-server forwarding +// adapter, signing test fixtures, personas. Per RFC §Reference: covers TS +// exports `/testing`, `/testing/personas`, `/conformance`, `/compliance`, +// `/compliance-fixtures`, `/substitution`, `/signing/testing`, `/mock-server`. +// Mock-server forwarding contract: storyboards certify against the shared +// @adcp/sdk/mock-server (D5/D8), not an in-process Java mock. + +plugins { + id("adcp.java-library-conventions") +} + +description = "AdCP Java SDK — storyboard runner, conformance harness, JUnit 5 fixtures" + +dependencies { + api(project(":adcp")) + // JUnit Jupiter is part of the public surface — adopters write tests against + // AdcpAgentExtension on the api scope. + api(libs.junit.jupiter.api) +} diff --git a/adcp-testing/src/main/java/org/adcontextprotocol/adcp/testing/package-info.java b/adcp-testing/src/main/java/org/adcontextprotocol/adcp/testing/package-info.java new file mode 100644 index 0000000..a979eaa --- /dev/null +++ b/adcp-testing/src/main/java/org/adcontextprotocol/adcp/testing/package-info.java @@ -0,0 +1,8 @@ +/** + * AdCP testing utilities — storyboard runner, conformance harness, + * JUnit 5 {@code AdcpAgentExtension}, mock-server forwarding adapter, + * personas. Mirrors {@code @adcp/sdk}'s {@code /testing}, {@code /conformance}, + * {@code /compliance}, and related exports. + */ +@org.jspecify.annotations.NullMarked +package org.adcontextprotocol.adcp.testing; diff --git a/adcp/build.gradle.kts b/adcp/build.gradle.kts new file mode 100644 index 0000000..84c15e3 --- /dev/null +++ b/adcp/build.gradle.kts @@ -0,0 +1,17 @@ +// adcp — caller, generated types, version co-existence, schema bundle. +// The main artifact. Per RFC §Reference: covers the TS exports `.`, `/client`, +// `/types`, `/types/v2-5`, `/auth`, `/advanced`, `/schemas`. + +plugins { + id("adcp.java-library-conventions") +} + +description = "AdCP Java SDK — caller, types, schema bundle" + +dependencies { + api(libs.jackson.databind) + api(libs.jackson.datatype.jsr310) + api(libs.slf4j.api) + api(libs.jspecify) + implementation(libs.json.schema.validator) +} diff --git a/adcp/src/main/java/org/adcontextprotocol/adcp/package-info.java b/adcp/src/main/java/org/adcontextprotocol/adcp/package-info.java new file mode 100644 index 0000000..262563b --- /dev/null +++ b/adcp/src/main/java/org/adcontextprotocol/adcp/package-info.java @@ -0,0 +1,9 @@ +/** + * AdCP Java SDK — caller, generated types, schema bundle accessor. + * + *

This is the main artifact. It mirrors {@code @adcp/sdk}'s {@code .}, + * {@code /client}, {@code /types}, {@code /types/v2-5}, {@code /auth}, + * {@code /advanced}, and {@code /schemas} exports. + */ +@org.jspecify.annotations.NullMarked +package org.adcontextprotocol.adcp; diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts new file mode 100644 index 0000000..ebb94b4 --- /dev/null +++ b/build-logic/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + `kotlin-dsl` +} + +dependencies { + // Expose the type-safe `libs` accessor inside precompiled script plugins. + // Documented workaround until Gradle ships first-class support; the + // reflective path resolves to the generated LibrariesForLibs jar. + implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location)) + + // Needed by adcp.kotlin-library-conventions to apply the Kotlin plugin. + implementation(libs.plugins.kotlin.jvm.map { + "org.jetbrains.kotlin:kotlin-gradle-plugin:${it.version}" + }) +} diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts new file mode 100644 index 0000000..d758bb1 --- /dev/null +++ b/build-logic/settings.gradle.kts @@ -0,0 +1,13 @@ +dependencyResolutionManagement { + repositories { + gradlePluginPortal() + mavenCentral() + } + versionCatalogs { + create("libs") { + from(files("../gradle/libs.versions.toml")) + } + } +} + +rootProject.name = "build-logic" diff --git a/build-logic/src/main/kotlin/adcp.java-base-conventions.gradle.kts b/build-logic/src/main/kotlin/adcp.java-base-conventions.gradle.kts new file mode 100644 index 0000000..625fbdf --- /dev/null +++ b/build-logic/src/main/kotlin/adcp.java-base-conventions.gradle.kts @@ -0,0 +1,66 @@ +// Common Java config applied to every JVM module in the SDK. +// Per D2: JDK 21 toolchain, no 17 fallback. +// Per D18: matches the family's conventional-commits / changesets shape +// at the build level (commitlint + changesets aren't a Gradle concern but +// the build-side conventions below — tests on every module, jar manifest +// stamping — are). + +plugins { + java +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } + withJavadocJar() + withSourcesJar() +} + +tasks.withType().configureEach { + options.release = 21 + options.encoding = "UTF-8" + // -Xlint:all minus `processing` (skeleton modules have @NullMarked + // package annotations with no claiming processor — a real warning, + // but not actionable until contributors fill the modules) and + // `options` (cross-version compile noise). + options.compilerArgs.addAll(listOf("-Xlint:all,-processing,-options", "-Werror")) +} + +tasks.withType().configureEach { + (options as StandardJavadocDocletOptions).apply { + encoding = "UTF-8" + addStringOption("Xdoclint:none", "-quiet") + } + // Tolerate package-info-only modules during the harness phase. Once + // each module has real public classes, this can flip back to strict. + isFailOnError = false +} + +tasks.withType().configureEach { + useJUnitPlatform() + testLogging { + events("failed", "skipped") + showExceptions = true + showStackTraces = true + exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL + } +} + +tasks.withType().configureEach { + manifest { + attributes( + "Automatic-Module-Name" to "org.adcontextprotocol.${project.name.replace('-', '.')}", + "Implementation-Title" to project.name, + "Implementation-Version" to project.version + ) + } +} + +// All modules get JSpecify nullability annotations on the compile classpath +// per RFC §Cross-cutting: no Optional returns; @Nullable T everywhere. +val libs = the() + +dependencies { + "compileOnly"(libs.jspecify) +} diff --git a/build-logic/src/main/kotlin/adcp.java-library-conventions.gradle.kts b/build-logic/src/main/kotlin/adcp.java-library-conventions.gradle.kts new file mode 100644 index 0000000..5ddfe24 --- /dev/null +++ b/build-logic/src/main/kotlin/adcp.java-library-conventions.gradle.kts @@ -0,0 +1,13 @@ +// Java library modules (everything except adcp-cli and adcp-kotlin). +plugins { + id("adcp.java-base-conventions") + `java-library` +} + +val libs = the() + +dependencies { + "testImplementation"(libs.junit.jupiter.api) + "testRuntimeOnly"(libs.junit.jupiter.engine) + "testRuntimeOnly"(libs.junit.platform.launcher) +} diff --git a/build-logic/src/main/kotlin/adcp.kotlin-library-conventions.gradle.kts b/build-logic/src/main/kotlin/adcp.kotlin-library-conventions.gradle.kts new file mode 100644 index 0000000..0b0f982 --- /dev/null +++ b/build-logic/src/main/kotlin/adcp.kotlin-library-conventions.gradle.kts @@ -0,0 +1,17 @@ +// Kotlin library module (adcp-kotlin per D14 — thin extension at v1.0). +plugins { + id("adcp.java-base-conventions") + id("org.jetbrains.kotlin.jvm") +} + +val libs = the() + +kotlin { + jvmToolchain(21) +} + +dependencies { + "testImplementation"(libs.junit.jupiter.api) + "testRuntimeOnly"(libs.junit.jupiter.engine) + "testRuntimeOnly"(libs.junit.platform.launcher) +} diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..4a8d85c --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,7 @@ +// Root build. Modules apply convention plugins from build-logic/. +// Per D3, group = org.adcontextprotocol; per D17, semver tags from main. + +allprojects { + group = "org.adcontextprotocol" + version = "0.1.0-SNAPSHOT" +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..a137000 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.parallel=true +org.gradle.caching=true +org.gradle.configuration-cache=true +org.gradle.jvmargs=-Xmx2g -XX:+UseG1GC -Dfile.encoding=UTF-8 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..8255437 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,75 @@ +# Version catalog for the AdCP Java SDK. +# All artifact versions live here. Module build files reference by alias. +# Bumping any version is a deliberate PR — D15 (spec-rev tracking) and D9 +# (MCP SDK pin) both depend on this being the single source of truth. + +[versions] +# Toolchain (D2 — JDK 21 only). +java = "21" +kotlin = "2.1.10" + +# Core runtime. +jackson = "2.18.2" +slf4j = "2.0.16" +jspecify = "1.0.0" + +# Schema validation (RFC §Schema validation). +json-schema-validator = "1.5.6" + +# MCP SDK (D9 — pinned 1.1.2; plan a deliberate 2.x migration PR ~6 months out). +mcp-sdk = "1.1.2" + +# Jakarta Servlet API — pulled in by mcp-core's HTTP server transport (D9 R1 +# prototype question: can it run without Jetty/Tomcat?). +jakarta-servlet = "6.1.0" + +# Test. +junit = "5.11.4" + +# Spring Boot (D7 — Spring Boot 3.x / jakarta only). +spring-boot = "3.4.4" + +# Reactor + Mutiny (D13 — both at GA, not fast-follow). +reactor = "3.7.2" +mutiny = "2.7.0" + +# Kotlin coroutines (D14 — co-released at v1.0 as a thin extension). +kotlinx-coroutines = "1.10.1" + +[libraries] +# Core +jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" } +jackson-datatype-jsr310 = { module = "com.fasterxml.jackson.datatype:jackson-datatype-jsr310", version.ref = "jackson" } +slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } +jspecify = { module = "org.jspecify:jspecify", version.ref = "jspecify" } + +# Validation +json-schema-validator = { module = "com.networknt:json-schema-validator", version.ref = "json-schema-validator" } + +# MCP +mcp = { module = "io.modelcontextprotocol.sdk:mcp", version.ref = "mcp-sdk" } + +# Servlet (for mcp-core HTTP transport) +jakarta-servlet-api = { module = "jakarta.servlet:jakarta.servlet-api", version.ref = "jakarta-servlet" } + +# Test +junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" } +junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" } +junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher" } + +# Spring Boot starter machinery (used only by adcp-spring-boot-starter) +spring-boot-autoconfigure = { module = "org.springframework.boot:spring-boot-autoconfigure", version.ref = "spring-boot" } +spring-boot-configuration-processor = { module = "org.springframework.boot:spring-boot-configuration-processor", version.ref = "spring-boot" } + +# Reactive bridges +reactor-core = { module = "io.projectreactor:reactor-core", version.ref = "reactor" } +mutiny = { module = "io.smallrye.reactive:mutiny", version.ref = "mutiny" } + +# Kotlin +kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } + +# CLI runtime logging +slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" } + +[plugins] +kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..b1b8ef56b44f16b14dc800fa8103a6d89abb526f GIT binary patch literal 48462 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 R2np~f1o-$>Q}y+){|A@R9n$~+ literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..df6a6ad --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,9 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip +networkTimeout=10000 +retries=0 +retryBackOffMs=500 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..b9bb139 --- /dev/null +++ b/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..aa5f10b --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,82 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel + +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..9d857d7 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,27 @@ +pluginManagement { + includeBuild("build-logic") + repositories { + gradlePluginPortal() + mavenCentral() + } +} + +dependencyResolutionManagement { + repositories { + mavenCentral() + } +} + +rootProject.name = "adcp-sdk-java" + +// Module list per D3 — eight artifacts. Order is dependency order: leaves first. +include( + "adcp", + "adcp-server", + "adcp-testing", + "adcp-spring-boot-starter", + "adcp-cli", + "adcp-reactor", + "adcp-mutiny", + "adcp-kotlin" +) From 3e294ddbbdee69386100e0a817c13de7554380c1 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Wed, 13 May 2026 16:37:00 -0400 Subject: [PATCH 05/12] docs: contributor onboarding (CONTRIBUTING, CLAUDE.md, templates) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CONTRIBUTING.md covers the AAO IPR comment ritual (per D19), dev setup, the track-claim flow into ROADMAP.md, code conventions, Conventional Commits + Changesets workflow, and the schema/codegen pointer. CLAUDE.md gives coding agents the precedence order (D-decisions > RFC > tracks), build commands, module layout, conventions, and a "don't do this" list pulled from the locked decisions (no *Async mirrors, no Spring Boot 2.7, no Bouncy Castle, no Optional returns, no docs/adr/). Issue templates: - track-claim.yml — structured form: track ID, experience, availability, milestone, WG sync, open questions - bug-report.yml — SDK version, JDK version, framework, repro - config.yml — disables blank issues; links protocol questions to the spec site, design discussion to AdCP Slack, sister-SDK bugs to those repos PR template prompts the IPR check, Conventional Commits, changeset addition, and the standard ./gradlew build + test plan checklist. .gitignore adds .claude/ and .context/ — coding-agent and Conductor workspace side channels that shouldn't be committed. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/ISSUE_TEMPLATE/bug-report.yml | 53 ++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 14 ++++ .github/ISSUE_TEMPLATE/track-claim.yml | 61 ++++++++++++++++++ .github/pull_request_template.md | 31 +++++++++ .gitignore | 4 ++ CLAUDE.md | 79 +++++++++++++++++++++++ CONTRIBUTING.md | 88 ++++++++++++++++++++++++++ 7 files changed, 330 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug-report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/track-claim.yml create mode 100644 .github/pull_request_template.md create mode 100644 CLAUDE.md create mode 100644 CONTRIBUTING.md diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 0000000..90f99df --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,53 @@ +name: Bug report +description: Report a defect in the SDK +labels: ["bug"] +body: + - type: input + id: sdk-version + attributes: + label: SDK version + description: Either a published `org.adcontextprotocol:adcp:X.Y.Z` coordinate or a commit SHA if you're on `main`. + placeholder: e.g. 0.1.0-SNAPSHOT (commit abc1234) + validations: + required: true + - type: input + id: jdk-version + attributes: + label: JDK version + description: Output of `java -version`. We baseline on JDK 21 per D2. + placeholder: e.g. openjdk 21.0.11 + validations: + required: true + - type: input + id: framework + attributes: + label: Framework / runtime + description: Spring Boot, Quarkus, plain JVM, etc. Include the version. + placeholder: e.g. Spring Boot 3.4.4 / plain JVM + - type: textarea + id: what-happened + attributes: + label: What happened + description: Symptom you observed. Stack trace if you have one. + validations: + required: true + - type: textarea + id: what-expected + attributes: + label: What you expected + validations: + required: true + - type: textarea + id: repro + attributes: + label: Minimal reproduction + description: | + Smallest code (or `./gradlew` invocation) that demonstrates the bug. The bar: a maintainer should be able to paste it into a fresh checkout and reproduce in under 5 minutes. + render: java + validations: + required: true + - type: textarea + id: notes + attributes: + label: Anything else + description: Context, hypotheses, related issues, anything that might help. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..5b80e59 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,14 @@ +blank_issues_enabled: false +contact_links: + - name: AdCP protocol questions + url: https://adcontextprotocol.org/ + about: Questions about the protocol itself (not the Java SDK) belong on the protocol site. + - name: Working group discussion + url: https://adcontextprotocol.slack.com + about: Design discussion, RFCs, and cross-SDK coordination happen in the AdCP Slack. + - name: TypeScript SDK + url: https://github.com/adcontextprotocol/adcp-client + about: Bugs or questions about the TypeScript SDK belong there, not here. + - name: Python SDK + url: https://github.com/adcontextprotocol/adcp-client-python + about: Bugs or questions about the Python SDK belong there, not here. diff --git a/.github/ISSUE_TEMPLATE/track-claim.yml b/.github/ISSUE_TEMPLATE/track-claim.yml new file mode 100644 index 0000000..48ae1ff --- /dev/null +++ b/.github/ISSUE_TEMPLATE/track-claim.yml @@ -0,0 +1,61 @@ +name: Claim a track +description: Volunteer to own one of the 14 tracks from ROADMAP.md +title: "[track:] claims " +labels: ["track-claim"] +body: + - type: markdown + attributes: + value: | + Thanks for offering to help build this SDK. Read [`ROADMAP.md`](https://github.com/adcontextprotocol/adcp-sdk-java/blob/main/ROADMAP.md) first — it covers the 14 tracks and their dependencies. Skim the [21 confirmed decisions](https://github.com/adcontextprotocol/adcp-sdk-java/blob/main/ROADMAP.md#confirmed-decisions) — they override the RFC where they differ. + - type: input + id: track + attributes: + label: Track ID + description: One of the 14 track IDs from ROADMAP.md (e.g. `signing`, `codegen`, `transport`, `spring`, `cli`, `kotlin`, `docs`, etc.) + placeholder: e.g. signing + validations: + required: true + - type: textarea + id: experience + attributes: + label: Relevant experience + description: | + Anything that informs whether this is a good fit — records, sealed types, Jackson, Spring Boot, RFC 9421 / JOSE signing, MCP, ad tech protocols, JVM publishing, etc. Be concrete; "5 years building publisher-facing JVM services with Spring Boot 3 + Postgres" beats "experienced Java developer." + validations: + required: true + - type: input + id: availability + attributes: + label: Estimated availability + description: Hours per week you can commit. Honest numbers help us plan dependencies. + placeholder: e.g. 10 hrs/week + validations: + required: true + - type: dropdown + id: milestone + attributes: + label: First milestone you're committing to + description: The track may span several; pick the one you're aiming at first. + options: + - v0.1 alpha (M+2) + - v0.2 alpha (M+4) + - v0.3 alpha (M+6) + - v0.4 beta (M+9) + - v1.0 GA (M+12) + validations: + required: true + - type: dropdown + id: wg-sync + attributes: + label: Can you attend the AdCP working-group sync? + options: + - "Yes — regularly" + - "Sometimes" + - "No — async only" + validations: + required: true + - type: textarea + id: questions + attributes: + label: Open questions before you start + description: Anything you'd want clarified before claiming. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..0459815 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,31 @@ + + +## Summary + + + +## Tracks / decisions touched + + + +## Test plan + + + +- [ ] `./gradlew build` green +- [ ] New tests added for new behavior +- [ ] Public API surface change → changeset added diff --git a/.gitignore b/.gitignore index 2a62b84..df5250f 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,10 @@ local.properties .project .settings/ +# Coding-agent side channels (Conductor workspace state, etc.) +.claude/ +.context/ + # OS .DS_Store diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..77d4270 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,79 @@ +# Repo guide for coding agents + +This file is for Claude Code, Cursor, Copilot, and other coding agents. Humans should start with [`README.md`](README.md) and [`CONTRIBUTING.md`](CONTRIBUTING.md). + +## What this repo is + +The Java SDK for the [AdCP protocol](https://adcontextprotocol.org). Targets v1.0 GA at full L0–L3 parity with `@adcp/sdk` (TypeScript) and `adcp` (Python). + +Status: harness phase. Eight Gradle modules wired and green; most surfaces are placeholder `package-info.java` files waiting for track owners. + +## Authoritative documents (read these before doing anything non-trivial) + +In order of precedence: + +1. **[`ROADMAP.md`](ROADMAP.md) §Confirmed decisions** — 21 numbered decisions (D1–D21) that lock in architecture, build, conventions. **These override the RFC where they differ.** +2. **[`docs/rfc/java-sdk-rfc.md`](docs/rfc/java-sdk-rfc.md)** — the merged RFC. +3. **[`ROADMAP.md`](ROADMAP.md) §Tracks** — what each module is supposed to contain, in track-claim form. +4. **[`ROADMAP.md`](ROADMAP.md) §7.x deltas** — what changed in the TS SDK between the RFC's 6.x baseline and the current 7.2.0. + +If a task contradicts a confirmed decision, **stop and ask** before coding around it. + +## Build commands + +JDK 21 required. The Gradle wrapper is committed. + +```bash +./gradlew build # full build, all 8 modules +./gradlew test # tests only +./gradlew :adcp-cli:run # run CLI placeholder +./gradlew clean # nuke build/ +``` + +CI runs `./gradlew build` on every PR via `.github/workflows/ci.yml`. + +## Module layout + +| Module | Java package | Role | +|---|---|---| +| `adcp` | `org.adcontextprotocol.adcp` | Main library — caller, generated types, schema bundle | +| `adcp-server` | `org.adcontextprotocol.adcp.server` | Agent-side: signing, idempotency, async tasks, webhooks | +| `adcp-testing` | `org.adcontextprotocol.adcp.testing` | Storyboard runner, conformance harness, JUnit 5 fixtures | +| `adcp-spring-boot-starter` | `org.adcontextprotocol.adcp.springboot` | Spring Boot 3.x autoconfig (D7: jakarta only) | +| `adcp-cli` | `org.adcontextprotocol.adcp.cli` | Runnable jar | +| `adcp-reactor` | `org.adcontextprotocol.adcp.reactor` | Project Reactor bridge | +| `adcp-mutiny` | `org.adcontextprotocol.adcp.mutiny` | SmallRye Mutiny bridge | +| `adcp-kotlin` | `org.adcontextprotocol.adcp.kotlin` | Kotlin coroutine + DSL extensions | + +Build infrastructure: +- `gradle/libs.versions.toml` — every external dep version. Bump versions here, never inline. +- `build-logic/` — convention plugins shared across modules. + +## Conventions to follow + +- **Java 21 records + sealed types** wherever they fit. No `Optional` returns — use `@Nullable T` (JSpecify) instead. +- **`*Request` builds, `*Response` doesn't.** This naming invariant prevents IDE auto-complete from suggesting `.builder()` on response types. +- **SLF4J for logging.** Not `java.util.logging`. +- **Conventional Commits** for commit messages. Types: `feat fix docs style refactor perf test build ci chore revert`. +- **Changesets** for adopter-visible changes (`npx changeset`). + +## Don't + +- Don't add `*Async` mirror methods. D2 (JDK 21 only) dropped them — virtual threads make sync API scale natively. +- Don't add Spring Boot 2.7 / `javax` compat (D7). +- Don't add Bouncy Castle to core (D2/D9 — JDK 21 has Ed25519 natively). +- Don't hand-edit files under `build/generated/` — they regenerate. +- Don't add `Optional` returns — use `@Nullable T`. +- Don't create `docs/adr/` — design decisions land in `ROADMAP.md` rows or `specs/.md` per D16. + +## Codegen + +The SDK generates Java types from JSON Schemas at `https://adcontextprotocol.org/protocol/{version}.tgz`. The generator is on the [`codegen` track](ROADMAP.md#track-2--l0-types--codegen) — not yet written. When it is, generator output lands in `adcp/build/generated/` and is **not** checked in. + +## Testing + +JUnit 5. Conventional test layout (`src/test/java/...`). Tests run with `./gradlew test`. Storyboard CI lives in `adcp-testing` and hits the `@adcp/sdk/mock-server` sidecar in CI (D8). + +## Picking up work + +The contributor onboarding path is in [`CONTRIBUTING.md`](CONTRIBUTING.md): claim a track via issue, work on a feature branch, PR to `main`. The 14 tracks live in [`ROADMAP.md`](ROADMAP.md#tracks). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..699f125 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,88 @@ +# Contributing to the AdCP Java SDK + +Contributions are welcome. Fork, branch, commit, [open a pull request](https://help.github.com/articles/using-pull-requests/) against `main`. + +## Intellectual Property Rights + +Before contributing, you must agree to the AAO [Intellectual Property Rights Policy](https://github.com/adcontextprotocol/adcp/blob/main/IPR_POLICY.md). When you open a pull request, the **AAO IPR Bot** will comment asking you to confirm by replying: + +``` +I have read the IPR Policy +``` + +This is a one-time requirement per contributor across all AAO repositories. The bot writes your signature to the central ledger in [`adcontextprotocol/adcp`](https://github.com/adcontextprotocol/adcp/blob/main/signatures/ipr-signatures.json), so once you've signed for any AAO repo, you're set for this one too. + +## What to work on + +Read [`ROADMAP.md`](ROADMAP.md) first. It divides v0.1 → v1.0 into 14 claimable tracks. To claim a track, open an issue with the **Claim a track** template: + +- Your prior JVM experience (records, sealed types, Jackson, Spring Boot, RFC 9421 signing if relevant). +- Estimated availability per week. +- Which milestone you're committing to first. + +A single contributor can claim multiple non-conflicting tracks. High-coupling tracks (`async-l3` + `lifecycle`) benefit from a single owner pair. + +Before designing anything new, read the [21 confirmed post-RFC decisions](ROADMAP.md#confirmed-decisions). They override the RFC where they differ. + +## Dev setup + +Requirements: +- JDK 21 (Temurin recommended) +- The Gradle wrapper (committed) — don't install Gradle separately. + +Local build: + +```bash +./gradlew build # compile + test all 8 modules +./gradlew :adcp-cli:run # run the CLI placeholder +./gradlew test # tests only +./gradlew check # tests + lint (alias) +``` + +CI runs the same command on every PR (`build` workflow, JDK 21 Temurin). The build is **green** if your PR passes. + +## Code conventions + +- **Java 21 baseline** (D2). Use records, sealed types, pattern matching, virtual threads. No `Optional` returns — use `@Nullable T` (JSpecify) instead. +- **Base package** `org.adcontextprotocol.adcp.*` (D3). Sub-packages by surface: `.task`, `.server`, `.signing`, `.testing`, etc. +- **Naming invariant** (RFC §Type generation): `*Request` types always have builders; `*Response` types are records and never do. This rule is what keeps coding-agent assistance (Claude / Copilot) honest. +- **Logging**: SLF4J only. No `java.util.logging`, no `commons-logging`. +- **Dependencies**: declare every external dep in [`gradle/libs.versions.toml`](gradle/libs.versions.toml). Don't pin versions inline in module `build.gradle.kts` files. + +## Conventional Commits + Changesets + +Commit messages and PR titles must follow [Conventional Commits](https://www.conventionalcommits.org/). Allowed types: + +``` +feat fix docs style refactor perf test build ci chore revert +``` + +PRs that change the public surface (anything an adopter would notice) need a changeset. Add one with: + +```bash +npx changeset +``` + +Pick the affected packages (one or more of the eight artifacts), pick a bump level (`patch` / `minor` / `major`), and write a short summary. The `changeset-check` CI job fails if a non-trivial PR is missing a changeset. + +For pure refactors, docs, or test-only PRs, no changeset is needed. + +## Design decisions + +Most design decisions live in [`ROADMAP.md`](ROADMAP.md#confirmed-decisions) as one-line table rows. When a decision needs more space, it gets its own `specs/.md` doc — matching the AdCP family convention (the Java SDK RFC itself lives at `specs/java-sdk-rfc.md` in the spec repo). + +If you propose changing a confirmed decision, open an issue first. PR-as-RFC ends in a stalled merge. + +## Schemas and generated code + +AdCP wire types are **generated** from the JSON Schemas published at `https://adcontextprotocol.org/protocol/{version}.tgz`. Don't hand-edit generated Java files — the next codegen run will overwrite them. If you need to fix a generated type, the fix lives in the generator (the `codegen` track), not the output. + +## Issues and questions + +- [Issues](https://github.com/adcontextprotocol/adcp-sdk-java/issues) for bugs and feature requests. +- [adcontextprotocol.org](https://adcontextprotocol.org/) for protocol-level documentation. +- The [AdCP Slack working group](https://adcontextprotocol.slack.com) for design discussion. + +## License + +This project is licensed under the [Apache License 2.0](LICENSE). See also the AAO [IPR Policy](https://github.com/adcontextprotocol/adcp/blob/main/IPR_POLICY.md). From 93a4ea7e65719056f8e24d3addcc4a0036ecdde0 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Wed, 13 May 2026 16:38:51 -0400 Subject: [PATCH 06/12] ci: AAO IPR Agreement, commitlint, changeset-check workflows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three workflows mirrored from adcp-client, matching the conventions locked by D18 (Conventional Commits + Changesets) and D19 (AAO IPR Bot pattern). ipr-agreement.yml delegates to the reusable workflow at adcontextprotocol/adcp/.github/workflows/ipr-check-callable.yml@main. Signatures land in the central ledger at adcp:signatures/ipr-signatures.json once the foundation admin (1) installs the AAO IPR Bot on this repo and (2) scopes IPR_APP_ID + IPR_APP_PRIVATE_KEY org secrets to it. commitlint.yml validates PR titles and all PR commits against the Conventional Commits config in commitlint.config.js. Type enum, header-max-length 120, and footer-leading-blank exception all match the @adcp/sdk config so the two SDK changelogs read the same way. changeset-check.yml requires a changeset on PRs that change adopter- visible behavior. Skips for changeset-release/main automation branches. package.json + package-lock.json define the npm-side tooling — explicit "private: true", scoped to commitlint + changesets devDeps only. The SDK itself is Java; this is the same shape adcp / adcp-client / adcp-go use for their release-notes machinery. .changeset/config.json points at the public Changesets schema with baseBranch main, restricted access (Java artifacts don't publish to npm), and the tools package ignored. README in the same directory explains when contributors should add a changeset vs skip. .gitignore adds node_modules/ — only for the Node-side tooling, the SDK remains pure Java. Co-Authored-By: Claude Opus 4.7 (1M context) --- .changeset/README.md | 32 + .changeset/config.json | 11 + .github/workflows/changeset-check.yml | 39 + .github/workflows/commitlint.yml | 35 + .github/workflows/ipr-agreement.yml | 26 + .gitignore | 4 + commitlint.config.js | 38 + package-lock.json | 2433 +++++++++++++++++++++++++ package.json | 15 + 9 files changed, 2633 insertions(+) create mode 100644 .changeset/README.md create mode 100644 .changeset/config.json create mode 100644 .github/workflows/changeset-check.yml create mode 100644 .github/workflows/commitlint.yml create mode 100644 .github/workflows/ipr-agreement.yml create mode 100644 commitlint.config.js create mode 100644 package-lock.json create mode 100644 package.json diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 0000000..95d7831 --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,32 @@ +# Changesets + +This folder is managed by `@changesets/cli`. Per [D18](../ROADMAP.md#confirmed-decisions), this repo uses the same release-notes workflow as `@adcp/sdk`. + +## When to add a changeset + +Add a changeset whenever your PR changes adopter-visible behavior on any of the eight published artifacts (`adcp`, `adcp-server`, `adcp-testing`, `adcp-spring-boot-starter`, `adcp-cli`, `adcp-reactor`, `adcp-mutiny`, `adcp-kotlin`). + +You don't need a changeset for: + +- Test-only changes +- Internal refactors with no public-API impact +- `docs:` commits that don't touch generated docs +- `chore:` / `ci:` commits + +The `changeset-check` workflow on PRs is informational for borderline cases — when in doubt, add one. + +## How to add a changeset + +```bash +npx changeset +``` + +Pick the affected artifacts, pick a bump level (`patch` / `minor` / `major`), and write a short user-facing summary. Commit the generated `.changeset/*.md` file with your PR. + +## Versioning + +Maven Central releases are produced from accumulated changesets. The first publish is **at v0.3 alpha** (per [D6](../ROADMAP.md#confirmed-decisions)); v0.1 and v0.2 ship as local Gradle artifacts only. SDK semver is independent of AdCP spec major (per RFC §Versioning). + +## More + +[Changesets docs](https://github.com/changesets/changesets) · [Common questions](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 0000000..3c5b58a --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json", + "changelog": "@changesets/cli/changelog", + "commit": false, + "fixed": [], + "linked": [], + "access": "restricted", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": ["adcp-sdk-java-tools"] +} diff --git a/.github/workflows/changeset-check.yml b/.github/workflows/changeset-check.yml new file mode 100644 index 0000000..6fc5119 --- /dev/null +++ b/.github/workflows/changeset-check.yml @@ -0,0 +1,39 @@ +name: changeset-check + +on: + pull_request: + branches: [main] + +permissions: + contents: read + +jobs: + check: + name: Check for changeset + runs-on: ubuntu-latest + + steps: + - name: Skip for release PRs + if: github.head_ref == 'changeset-release/main' + run: echo "Release PR — changeset not required" + + - name: Checkout code + if: github.head_ref != 'changeset-release/main' + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Setup Node.js + if: github.head_ref != 'changeset-release/main' + uses: actions/setup-node@v5 + with: + node-version: '20.x' + cache: 'npm' + + - name: Install dependencies + if: github.head_ref != 'changeset-release/main' + run: npm ci + + - name: Check for changeset + if: github.head_ref != 'changeset-release/main' + run: npx changeset status --since=origin/main diff --git a/.github/workflows/commitlint.yml b/.github/workflows/commitlint.yml new file mode 100644 index 0000000..40af32a --- /dev/null +++ b/.github/workflows/commitlint.yml @@ -0,0 +1,35 @@ +name: Lint Commit Messages + +on: + pull_request: + types: [opened, synchronize, reopened, edited] + +permissions: + contents: read + +jobs: + commitlint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v5 + with: + node-version: '20.x' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Validate PR title follows conventional commits + env: + PR_TITLE: ${{ github.event.pull_request.title }} + run: | + echo "$PR_TITLE" | npx commitlint --config commitlint.config.js + + - name: Validate all commits in PR + run: | + npx commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verbose diff --git a/.github/workflows/ipr-agreement.yml b/.github/workflows/ipr-agreement.yml new file mode 100644 index 0000000..69f2a8f --- /dev/null +++ b/.github/workflows/ipr-agreement.yml @@ -0,0 +1,26 @@ +name: IPR Agreement + +# Delegates to the reusable workflow in adcontextprotocol/adcp. Signatures +# land in the central ledger at adcontextprotocol/adcp:signatures/ipr-signatures.json. +# See https://github.com/adcontextprotocol/adcp/blob/main/governance/ipr-bot-setup.md +# for the GitHub App configuration and how to rotate / revoke credentials. +# +# Per D19 (ROADMAP §Confirmed decisions): this repo replicates the same IPR +# pattern installed on adcp / adcp-client / adcp-client-python / adcp-go. + +on: + issue_comment: + types: [created] + pull_request_target: + types: [opened, synchronize, reopened] + +permissions: + pull-requests: write + statuses: write + +jobs: + check: + uses: adcontextprotocol/adcp/.github/workflows/ipr-check-callable.yml@main + secrets: + IPR_APP_ID: ${{ secrets.IPR_APP_ID }} + IPR_APP_PRIVATE_KEY: ${{ secrets.IPR_APP_PRIVATE_KEY }} diff --git a/.gitignore b/.gitignore index df5250f..46624af 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,10 @@ build/ local.properties +# Node — only for commitlint + Changesets tooling (per D18); the SDK itself is Java +node_modules/ +npm-debug.log* + # IDE .idea/ *.iml diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 0000000..3f108cf --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1,38 @@ +// Conventional Commits config — mirrors @adcp/sdk's so humans and tools +// read both SDK changelogs the same way (per D18). + +module.exports = { + extends: ['@commitlint/config-conventional'], + parserPreset: 'conventional-changelog-conventionalcommits', + rules: { + 'type-enum': [ + 2, + 'always', + [ + 'feat', // New feature + 'fix', // Bug fix + 'docs', // Documentation only + 'style', // Code style changes (formatting, missing semicolons, etc) + 'refactor', // Code refactoring + 'perf', // Performance improvements + 'test', // Adding or updating tests + 'build', // Changes to build system or dependencies + 'ci', // Changes to CI configuration + 'chore', // Other changes that don't modify src or test files + 'revert', // Reverts a previous commit + ], + ], + 'scope-empty': [0], // Allow empty scope + 'subject-case': [0], // Don't enforce subject case + 'body-max-line-length': [0], // Disable body line length limit + // Raised from default 100 to 120, matching the TS SDK. Long-running + // review-round commits ("address round-N review — ...") legitimately + // need the breathing room for the scope + colon + dash-separated items. + 'header-max-length': [2, 'always', 120], + // Disabled: bodies that legitimately use lines like `Status: …` get + // parsed as trailers, then the next paragraph triggers a false-positive + // "footer must have leading blank line" warning. Stylistic; not worth + // gating CI on parser edge-cases. + 'footer-leading-blank': [0], + }, +}; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..908abe6 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2433 @@ +{ + "name": "adcp-sdk-java-tools", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "adcp-sdk-java-tools", + "devDependencies": { + "@changesets/cli": "^2.29.7", + "@commitlint/cli": "^19.6.0", + "@commitlint/config-conventional": "^19.6.0", + "conventional-changelog-conventionalcommits": "^8.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@changesets/apply-release-plan": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-7.1.1.tgz", + "integrity": "sha512-9qPCm/rLx/xoOFXIHGB229+4GOL76S4MC+7tyOuTsR6+1jYlfFDQORdvwR5hDA6y4FL2BPt3qpbcQIS+dW85LA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/config": "^3.1.4", + "@changesets/get-version-range-type": "^0.4.0", + "@changesets/git": "^3.0.4", + "@changesets/should-skip-package": "^0.1.2", + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3", + "detect-indent": "^6.0.0", + "fs-extra": "^7.0.1", + "lodash.startcase": "^4.4.0", + "outdent": "^0.5.0", + "prettier": "^2.7.1", + "resolve-from": "^5.0.0", + "semver": "^7.5.3" + } + }, + "node_modules/@changesets/assemble-release-plan": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-6.0.10.tgz", + "integrity": "sha512-rSDcqdJ9KbVyjpBIuCidhvZNIiVt1XaIYp73ycVQRIA5n/j6wQaEk0ChRLMUQ1vkxZe51PTQ9OIhbg6HQMW45A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/errors": "^0.2.0", + "@changesets/get-dependents-graph": "^2.1.4", + "@changesets/should-skip-package": "^0.1.2", + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3", + "semver": "^7.5.3" + } + }, + "node_modules/@changesets/changelog-git": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@changesets/changelog-git/-/changelog-git-0.2.1.tgz", + "integrity": "sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^6.1.0" + } + }, + "node_modules/@changesets/cli": { + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/@changesets/cli/-/cli-2.31.0.tgz", + "integrity": "sha512-AhI4enNTgHu2IZr6K4WZyf0EPch4XVMn1yOMFmCD9gsfBGqMYaHXls5HyDv6/CL5axVQABz68eG30eCtbr2wFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/apply-release-plan": "^7.1.1", + "@changesets/assemble-release-plan": "^6.0.10", + "@changesets/changelog-git": "^0.2.1", + "@changesets/config": "^3.1.4", + "@changesets/errors": "^0.2.0", + "@changesets/get-dependents-graph": "^2.1.4", + "@changesets/get-release-plan": "^4.0.16", + "@changesets/git": "^3.0.4", + "@changesets/logger": "^0.1.1", + "@changesets/pre": "^2.0.2", + "@changesets/read": "^0.6.7", + "@changesets/should-skip-package": "^0.1.2", + "@changesets/types": "^6.1.0", + "@changesets/write": "^0.4.0", + "@inquirer/external-editor": "^1.0.2", + "@manypkg/get-packages": "^1.1.3", + "ansi-colors": "^4.1.3", + "enquirer": "^2.4.1", + "fs-extra": "^7.0.1", + "mri": "^1.2.0", + "package-manager-detector": "^0.2.0", + "picocolors": "^1.1.0", + "resolve-from": "^5.0.0", + "semver": "^7.5.3", + "spawndamnit": "^3.0.1", + "term-size": "^2.1.0" + }, + "bin": { + "changeset": "bin.js" + } + }, + "node_modules/@changesets/config": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@changesets/config/-/config-3.1.4.tgz", + "integrity": "sha512-pf0bvD/v6WI2cRlZ6hzpjtZdSlXDXMAJ+Iz7xfFzV4ZxJ8OGGAON+1qYc99ZPrijnt4xp3VGG7eNvAOGS24V1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/errors": "^0.2.0", + "@changesets/get-dependents-graph": "^2.1.4", + "@changesets/logger": "^0.1.1", + "@changesets/should-skip-package": "^0.1.2", + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3", + "fs-extra": "^7.0.1", + "micromatch": "^4.0.8" + } + }, + "node_modules/@changesets/errors": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@changesets/errors/-/errors-0.2.0.tgz", + "integrity": "sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==", + "dev": true, + "license": "MIT", + "dependencies": { + "extendable-error": "^0.1.5" + } + }, + "node_modules/@changesets/get-dependents-graph": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-2.1.4.tgz", + "integrity": "sha512-ZsS00x6WvmHq3sQv8oCMwL0f/z3wbXCVuSVTJwCnnmbC/iBdNJGFx1EcbMG4PC6sXRyH69liM4A2WKXzn/kRPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3", + "picocolors": "^1.1.0", + "semver": "^7.5.3" + } + }, + "node_modules/@changesets/get-release-plan": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@changesets/get-release-plan/-/get-release-plan-4.0.16.tgz", + "integrity": "sha512-2K5Om6CrMPm45rtvckfzWo7e9jOVCKLCnXia5eUPaURH7/LWzri7pK1TycdzAuAtehLkW7VPbWLCSExTHmiI6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/assemble-release-plan": "^6.0.10", + "@changesets/config": "^3.1.4", + "@changesets/pre": "^2.0.2", + "@changesets/read": "^0.6.7", + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3" + } + }, + "node_modules/@changesets/get-version-range-type": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@changesets/get-version-range-type/-/get-version-range-type-0.4.0.tgz", + "integrity": "sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@changesets/git": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@changesets/git/-/git-3.0.4.tgz", + "integrity": "sha512-BXANzRFkX+XcC1q/d27NKvlJ1yf7PSAgi8JG6dt8EfbHFHi4neau7mufcSca5zRhwOL8j9s6EqsxmT+s+/E6Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/errors": "^0.2.0", + "@manypkg/get-packages": "^1.1.3", + "is-subdir": "^1.1.1", + "micromatch": "^4.0.8", + "spawndamnit": "^3.0.1" + } + }, + "node_modules/@changesets/logger": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@changesets/logger/-/logger-0.1.1.tgz", + "integrity": "sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.1.0" + } + }, + "node_modules/@changesets/parse": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@changesets/parse/-/parse-0.4.3.tgz", + "integrity": "sha512-ZDmNc53+dXdWEv7fqIUSgRQOLYoUom5Z40gmLgmATmYR9NbL6FJJHwakcCpzaeCy+1D0m0n7mT4jj2B/MQPl7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^6.1.0", + "js-yaml": "^4.1.1" + } + }, + "node_modules/@changesets/pre": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@changesets/pre/-/pre-2.0.2.tgz", + "integrity": "sha512-HaL/gEyFVvkf9KFg6484wR9s0qjAXlZ8qWPDkTyKF6+zqjBe/I2mygg3MbpZ++hdi0ToqNUF8cjj7fBy0dg8Ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/errors": "^0.2.0", + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3", + "fs-extra": "^7.0.1" + } + }, + "node_modules/@changesets/read": { + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/@changesets/read/-/read-0.6.7.tgz", + "integrity": "sha512-D1G4AUYGrBEk8vj8MGwf75k9GpN6XL3wg8i42P2jZZwFLXnlr2Pn7r9yuQNbaMCarP7ZQWNJbV6XLeysAIMhTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/git": "^3.0.4", + "@changesets/logger": "^0.1.1", + "@changesets/parse": "^0.4.3", + "@changesets/types": "^6.1.0", + "fs-extra": "^7.0.1", + "p-filter": "^2.1.0", + "picocolors": "^1.1.0" + } + }, + "node_modules/@changesets/should-skip-package": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@changesets/should-skip-package/-/should-skip-package-0.1.2.tgz", + "integrity": "sha512-qAK/WrqWLNCP22UDdBTMPH5f41elVDlsNyat180A33dWxuUDyNpg6fPi/FyTZwRriVjg0L8gnjJn2F9XAoF0qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3" + } + }, + "node_modules/@changesets/types": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@changesets/types/-/types-6.1.0.tgz", + "integrity": "sha512-rKQcJ+o1nKNgeoYRHKOS07tAMNd3YSN0uHaJOZYjBAgxfV7TUE7JE+z4BzZdQwb5hKaYbayKN5KrYV7ODb2rAA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@changesets/write": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@changesets/write/-/write-0.4.0.tgz", + "integrity": "sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^6.1.0", + "fs-extra": "^7.0.1", + "human-id": "^4.1.1", + "prettier": "^2.7.1" + } + }, + "node_modules/@commitlint/cli": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-19.8.1.tgz", + "integrity": "sha512-LXUdNIkspyxrlV6VDHWBmCZRtkEVRpBKxi2Gtw3J54cGWhLCTouVD/Q6ZSaSvd2YaDObWK8mDjrz3TIKtaQMAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/format": "^19.8.1", + "@commitlint/lint": "^19.8.1", + "@commitlint/load": "^19.8.1", + "@commitlint/read": "^19.8.1", + "@commitlint/types": "^19.8.1", + "tinyexec": "^1.0.0", + "yargs": "^17.0.0" + }, + "bin": { + "commitlint": "cli.js" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/config-conventional": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-19.8.1.tgz", + "integrity": "sha512-/AZHJL6F6B/G959CsMAzrPKKZjeEiAVifRyEwXxcT6qtqbPwGw+iQxmNS+Bu+i09OCtdNRW6pNpBvgPrtMr9EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^19.8.1", + "conventional-changelog-conventionalcommits": "^7.0.2" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/config-conventional/node_modules/conventional-changelog-conventionalcommits": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-7.0.2.tgz", + "integrity": "sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==", + "dev": true, + "license": "ISC", + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@commitlint/config-validator": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-19.8.1.tgz", + "integrity": "sha512-0jvJ4u+eqGPBIzzSdqKNX1rvdbSU1lPNYlfQQRIFnBgLy26BtC0cFnr7c/AyuzExMxWsMOte6MkTi9I3SQ3iGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^19.8.1", + "ajv": "^8.11.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/ensure": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-19.8.1.tgz", + "integrity": "sha512-mXDnlJdvDzSObafjYrOSvZBwkD01cqB4gbnnFuVyNpGUM5ijwU/r/6uqUmBXAAOKRfyEjpkGVZxaDsCVnHAgyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^19.8.1", + "lodash.camelcase": "^4.3.0", + "lodash.kebabcase": "^4.1.1", + "lodash.snakecase": "^4.1.1", + "lodash.startcase": "^4.4.0", + "lodash.upperfirst": "^4.3.1" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/execute-rule": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-19.8.1.tgz", + "integrity": "sha512-YfJyIqIKWI64Mgvn/sE7FXvVMQER/Cd+s3hZke6cI1xgNT/f6ZAz5heND0QtffH+KbcqAwXDEE1/5niYayYaQA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/format": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-19.8.1.tgz", + "integrity": "sha512-kSJj34Rp10ItP+Eh9oCItiuN/HwGQMXBnIRk69jdOwEW9llW9FlyqcWYbHPSGofmjsqeoxa38UaEA5tsbm2JWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^19.8.1", + "chalk": "^5.3.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/is-ignored": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-19.8.1.tgz", + "integrity": "sha512-AceOhEhekBUQ5dzrVhDDsbMaY5LqtN8s1mqSnT2Kz1ERvVZkNihrs3Sfk1Je/rxRNbXYFzKZSHaPsEJJDJV8dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^19.8.1", + "semver": "^7.6.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/lint": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-19.8.1.tgz", + "integrity": "sha512-52PFbsl+1EvMuokZXLRlOsdcLHf10isTPlWwoY1FQIidTsTvjKXVXYb7AvtpWkDzRO2ZsqIgPK7bI98x8LRUEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/is-ignored": "^19.8.1", + "@commitlint/parse": "^19.8.1", + "@commitlint/rules": "^19.8.1", + "@commitlint/types": "^19.8.1" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/load": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-19.8.1.tgz", + "integrity": "sha512-9V99EKG3u7z+FEoe4ikgq7YGRCSukAcvmKQuTtUyiYPnOd9a2/H9Ak1J9nJA1HChRQp9OA/sIKPugGS+FK/k1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/config-validator": "^19.8.1", + "@commitlint/execute-rule": "^19.8.1", + "@commitlint/resolve-extends": "^19.8.1", + "@commitlint/types": "^19.8.1", + "chalk": "^5.3.0", + "cosmiconfig": "^9.0.0", + "cosmiconfig-typescript-loader": "^6.1.0", + "lodash.isplainobject": "^4.0.6", + "lodash.merge": "^4.6.2", + "lodash.uniq": "^4.5.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/message": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-19.8.1.tgz", + "integrity": "sha512-+PMLQvjRXiU+Ae0Wc+p99EoGEutzSXFVwQfa3jRNUZLNW5odZAyseb92OSBTKCu+9gGZiJASt76Cj3dLTtcTdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/parse": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-19.8.1.tgz", + "integrity": "sha512-mmAHYcMBmAgJDKWdkjIGq50X4yB0pSGpxyOODwYmoexxxiUCy5JJT99t1+PEMK7KtsCtzuWYIAXYAiKR+k+/Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^19.8.1", + "conventional-changelog-angular": "^7.0.0", + "conventional-commits-parser": "^5.0.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/read": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-19.8.1.tgz", + "integrity": "sha512-03Jbjb1MqluaVXKHKRuGhcKWtSgh3Jizqy2lJCRbRrnWpcM06MYm8th59Xcns8EqBYvo0Xqb+2DoZFlga97uXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/top-level": "^19.8.1", + "@commitlint/types": "^19.8.1", + "git-raw-commits": "^4.0.0", + "minimist": "^1.2.8", + "tinyexec": "^1.0.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/resolve-extends": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-19.8.1.tgz", + "integrity": "sha512-GM0mAhFk49I+T/5UCYns5ayGStkTt4XFFrjjf0L4S26xoMTSkdCf9ZRO8en1kuopC4isDFuEm7ZOm/WRVeElVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/config-validator": "^19.8.1", + "@commitlint/types": "^19.8.1", + "global-directory": "^4.0.1", + "import-meta-resolve": "^4.0.0", + "lodash.mergewith": "^4.6.2", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/rules": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-19.8.1.tgz", + "integrity": "sha512-Hnlhd9DyvGiGwjfjfToMi1dsnw1EXKGJNLTcsuGORHz6SS9swRgkBsou33MQ2n51/boIDrbsg4tIBbRpEWK2kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/ensure": "^19.8.1", + "@commitlint/message": "^19.8.1", + "@commitlint/to-lines": "^19.8.1", + "@commitlint/types": "^19.8.1" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/to-lines": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-19.8.1.tgz", + "integrity": "sha512-98Mm5inzbWTKuZQr2aW4SReY6WUukdWXuZhrqf1QdKPZBCCsXuG87c+iP0bwtD6DBnmVVQjgp4whoHRVixyPBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/top-level": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-19.8.1.tgz", + "integrity": "sha512-Ph8IN1IOHPSDhURCSXBz44+CIu+60duFwRsg6HqaISFHQHbmBtxVw4ZrFNIYUzEP7WwrNPxa2/5qJ//NK1FGcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^7.0.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/types": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-19.8.1.tgz", + "integrity": "sha512-/yCrWGCoA1SVKOks25EGadP9Pnj0oAIHGpl2wH2M2Y46dPM2ueb8wyCVOD7O3WCTkaJ0IkKvzhl1JY7+uCT2Dw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/conventional-commits-parser": "^5.0.0", + "chalk": "^5.3.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@manypkg/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@manypkg/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.5.5", + "@types/node": "^12.7.1", + "find-up": "^4.1.0", + "fs-extra": "^8.1.0" + } + }, + "node_modules/@manypkg/find-root/node_modules/@types/node": { + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@manypkg/find-root/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@manypkg/find-root/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/@manypkg/find-root/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@manypkg/find-root/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@manypkg/find-root/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@manypkg/find-root/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@manypkg/get-packages": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@manypkg/get-packages/-/get-packages-1.1.3.tgz", + "integrity": "sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.5.5", + "@changesets/types": "^4.0.1", + "@manypkg/find-root": "^1.1.0", + "fs-extra": "^8.1.0", + "globby": "^11.0.0", + "read-yaml-file": "^1.1.0" + } + }, + "node_modules/@manypkg/get-packages/node_modules/@changesets/types": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@changesets/types/-/types-4.1.0.tgz", + "integrity": "sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@manypkg/get-packages/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/conventional-commits-parser": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.2.tgz", + "integrity": "sha512-BgT2szDXnVypgpNxOK8aL5SGjUdaQbC++WZNjF1Qge3Og2+zhHj+RWhmehLhYyvQwqAmvezruVfOf8+3m74W+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "25.7.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.7.0.tgz", + "integrity": "sha512-z+pdZyxE+RTQE9AcboAZCb4otwcrvgHD+GlBpPgn0emDVt0ohrTMhAwlr2Wd9nZ+nihhYFxO2pThz3C5qSu2Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.21.0" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", + "dev": true, + "license": "MIT" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/better-path-resolve": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/better-path-resolve/-/better-path-resolve-1.0.0.tgz", + "integrity": "sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-windows": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chardet": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", + "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + }, + "node_modules/conventional-changelog-angular": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz", + "integrity": "sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-changelog-conventionalcommits": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-8.0.0.tgz", + "integrity": "sha512-eOvlTO6OcySPyyyk8pKz2dP4jjElYunj9hn9/s0OB+gapTO8zwS9UQWrZ1pmF2hFs3vw1xhonOLGcGjy/zgsuA==", + "dev": true, + "license": "ISC", + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/conventional-commits-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz", + "integrity": "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-text-path": "^2.0.0", + "JSONStream": "^1.3.5", + "meow": "^12.0.1", + "split2": "^4.0.0" + }, + "bin": { + "conventional-commits-parser": "cli.mjs" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/cosmiconfig": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz", + "integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cosmiconfig-typescript-loader": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-6.3.0.tgz", + "integrity": "sha512-Akr82WH1Wfqatyiqpj8HDkO2o2KmJRu1FhKfSNJP3K4IdXwHfEyL7MOb62i1AGQVLtIQM+iCE9CGOtrfhR+mmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jiti": "2.6.1" + }, + "engines": { + "node": ">=v18" + }, + "peerDependencies": { + "@types/node": "*", + "cosmiconfig": ">=9", + "typescript": ">=5" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/dargs": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/dargs/-/dargs-8.1.0.tgz", + "integrity": "sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/detect-indent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/enquirer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/extendable-error": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/extendable-error/-/extendable-error-0.1.7.tgz", + "integrity": "sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-7.0.0.tgz", + "integrity": "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^7.2.0", + "path-exists": "^5.0.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/git-raw-commits": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-4.0.0.tgz", + "integrity": "sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==", + "deprecated": "This package is no longer maintained. For the JavaScript API, please use @conventional-changelog/git-client instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "dargs": "^8.0.0", + "meow": "^12.0.1", + "split2": "^4.0.0" + }, + "bin": { + "git-raw-commits": "cli.mjs" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/global-directory": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", + "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "4.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/human-id": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/human-id/-/human-id-4.1.3.tgz", + "integrity": "sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q==", + "dev": true, + "license": "MIT", + "bin": { + "human-id": "dist/cli.js" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ini": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", + "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-subdir": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-subdir/-/is-subdir-1.2.0.tgz", + "integrity": "sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "better-path-resolve": "1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/is-text-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-2.0.0.tgz", + "integrity": "sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "text-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "license": "MIT" + }, + "node_modules/JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "license": "(MIT OR Apache-2.0)", + "dependencies": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "bin.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.kebabcase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", + "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.snakecase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", + "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.startcase": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", + "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.upperfirst": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz", + "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/meow": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", + "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/outdent": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/outdent/-/outdent-0.5.0.tgz", + "integrity": "sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/p-filter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz", + "integrity": "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-map": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-manager-detector": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.11.tgz", + "integrity": "sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "quansync": "^0.2.7" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/read-yaml-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-yaml-file/-/read-yaml-file-1.1.0.tgz", + "integrity": "sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.5", + "js-yaml": "^3.6.1", + "pify": "^4.0.1", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/read-yaml-file/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/read-yaml-file/node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/spawndamnit": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spawndamnit/-/spawndamnit-3.0.1.tgz", + "integrity": "sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==", + "dev": true, + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "cross-spawn": "^7.0.5", + "signal-exit": "^4.0.1" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/term-size": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", + "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/text-extensions": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz", + "integrity": "sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz", + "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.21.0.tgz", + "integrity": "sha512-w9IMgQrz4O0YN1LtB7K5P63vhlIOvC7opSmouCJ+ZywlPAlO9gIkJ+otk6LvGpAs2wg4econaCz3TvQ9xPoyuQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..4cc4f93 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "private": true, + "name": "adcp-sdk-java-tools", + "description": "Node-side tooling for the AdCP Java SDK repo — commitlint and Changesets. Not published; not the SDK.", + "scripts": { + "changeset": "changeset", + "commitlint": "commitlint" + }, + "devDependencies": { + "@changesets/cli": "^2.29.7", + "@commitlint/cli": "^19.6.0", + "@commitlint/config-conventional": "^19.6.0", + "conventional-changelog-conventionalcommits": "^8.0.0" + } +} From 3ca606285d7c5454c27951ffe3be30517bddb7d5 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Wed, 13 May 2026 16:42:35 -0400 Subject: [PATCH 07/12] feat(http): SSRF-safe policy skeleton + spec doc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First piece of the 7.x SSRF baseline that ROADMAP §7.x deltas pinned as a v0.1 release-gate requirement (not v1.x hardening). Lands the address-guard half today; DNS-pin connect-binding and redirect handling land on the transport track. specs/ssrf-baseline.md is the design doc (per D16 — design notes live in specs/, not docs/adr/). Covers the threat model, the four mitigations (DNS-resolve-once, pin-connect, redirect:manual, body cap), the full RFC-grounded block table, the harness/transport split, and the references back to the TS SDK migration PRs. adcp module gains org.adcontextprotocol.adcp.http with: - SsrfPolicy — sealed interface; strict() is the v0.1 baseline, permissive() is opt-in for local dev (deliberately not env-driven so a misconfigured production deploy can't silently disable the guard). - SsrfDecision — sealed Allow/Deny; Deny reasons describe ranges, not specific addresses, so the reason text doesn't leak host structure when echoed in logs. - StrictSsrfPolicy — walks InetAddress's built-in range predicates (loopback / link-local / site-local / multicast / any-local) and layers in the JDK-not-covered cases by hand: - RFC 6598 CGN (100.64/10) - RFC 2544 benchmark (198.18/15) - RFC 6890 IETF protocol assignments (192.0.0/24) - Reserved class E (240/4) - IPv6 unique local (fc00::/7) - IPv4-mapped IPv6 (::ffff:a.b.c.d/96) — unmapped and re-evaluated against the v4 rules. NB: deliberately not using Inet6Address.isIPv4CompatibleAddress() because it matches the deprecated "::a.b.c.d" form (including ::1), which would cause loopback to be treated as 0.0.0.1 and pass the guard. - PermissiveSsrfPolicy — every address allowed; documented opt-in only. Tests cover 26 literal block-list addresses, four allowed public ones, the no-host-structure-leak property, and the permissive policy's allow-everything behavior. 35 tests, all green. Build-logic gains junit-jupiter-params on the test classpath so parameterized tests can express the block-list table compactly. .gitignore picks up .kotlin/ (build cache from the Kotlin DSL). The DNS-rebinding and redirect-follow integration tests are deferred to the transport track per the spec doc's harness/track split table. Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitignore | 1 + adcp-cli/build.gradle.kts | 1 + .../adcp/http/PermissiveSsrfPolicy.java | 23 ++++ .../adcp/http/SsrfDecision.java | 23 ++++ .../adcp/http/SsrfPolicy.java | 44 +++++++ .../adcp/http/StrictSsrfPolicy.java | 121 ++++++++++++++++++ .../adcp/http/StrictSsrfPolicyTest.java | 92 +++++++++++++ .../adcp.java-library-conventions.gradle.kts | 1 + ...adcp.kotlin-library-conventions.gradle.kts | 1 + gradle/libs.versions.toml | 1 + specs/ssrf-baseline.md | 111 ++++++++++++++++ 11 files changed, 419 insertions(+) create mode 100644 adcp/src/main/java/org/adcontextprotocol/adcp/http/PermissiveSsrfPolicy.java create mode 100644 adcp/src/main/java/org/adcontextprotocol/adcp/http/SsrfDecision.java create mode 100644 adcp/src/main/java/org/adcontextprotocol/adcp/http/SsrfPolicy.java create mode 100644 adcp/src/main/java/org/adcontextprotocol/adcp/http/StrictSsrfPolicy.java create mode 100644 adcp/src/test/java/org/adcontextprotocol/adcp/http/StrictSsrfPolicyTest.java create mode 100644 specs/ssrf-baseline.md diff --git a/.gitignore b/.gitignore index 46624af..2c9dacd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Gradle .gradle/ +.kotlin/ build/ local.properties diff --git a/adcp-cli/build.gradle.kts b/adcp-cli/build.gradle.kts index c7651c8..d462ad7 100644 --- a/adcp-cli/build.gradle.kts +++ b/adcp-cli/build.gradle.kts @@ -15,6 +15,7 @@ dependencies { runtimeOnly(libs.slf4j.simple) testImplementation(libs.junit.jupiter.api) + testImplementation(libs.junit.jupiter.params) testRuntimeOnly(libs.junit.jupiter.engine) testRuntimeOnly(libs.junit.platform.launcher) } diff --git a/adcp/src/main/java/org/adcontextprotocol/adcp/http/PermissiveSsrfPolicy.java b/adcp/src/main/java/org/adcontextprotocol/adcp/http/PermissiveSsrfPolicy.java new file mode 100644 index 0000000..8f0bf02 --- /dev/null +++ b/adcp/src/main/java/org/adcontextprotocol/adcp/http/PermissiveSsrfPolicy.java @@ -0,0 +1,23 @@ +package org.adcontextprotocol.adcp.http; + +import java.net.InetAddress; + +/** + * Permissive SSRF policy — local development only. Allows every address. + * + *

Documented opt-in per-request (never global). Wiring this as a default + * in production should fail loud — it deliberately doesn't read any + * environment variable or system property so a misconfigured deploy can't + * silently disable the guard. + */ +final class PermissiveSsrfPolicy implements SsrfPolicy { + + static final PermissiveSsrfPolicy INSTANCE = new PermissiveSsrfPolicy(); + + private PermissiveSsrfPolicy() {} + + @Override + public SsrfDecision evaluate(InetAddress address) { + return SsrfDecision.ALLOW; + } +} diff --git a/adcp/src/main/java/org/adcontextprotocol/adcp/http/SsrfDecision.java b/adcp/src/main/java/org/adcontextprotocol/adcp/http/SsrfDecision.java new file mode 100644 index 0000000..a2fd49b --- /dev/null +++ b/adcp/src/main/java/org/adcontextprotocol/adcp/http/SsrfDecision.java @@ -0,0 +1,23 @@ +package org.adcontextprotocol.adcp.http; + +/** + * Outcome of an {@link SsrfPolicy} check against a candidate target address. + * Sealed so call sites pattern-match against the two cases without a + * default branch. + */ +public sealed interface SsrfDecision { + + /** The address is allowed; the request may proceed. */ + record Allow() implements SsrfDecision {} + + /** + * The address is blocked. {@code reason} is a short, log-friendly + * description; safe to include in error envelopes since it never + * carries the rejected address itself (which could leak host + * structure to an attacker). + */ + record Deny(String reason) implements SsrfDecision {} + + /** Singleton {@link Allow} instance. */ + Allow ALLOW = new Allow(); +} diff --git a/adcp/src/main/java/org/adcontextprotocol/adcp/http/SsrfPolicy.java b/adcp/src/main/java/org/adcontextprotocol/adcp/http/SsrfPolicy.java new file mode 100644 index 0000000..b8baf77 --- /dev/null +++ b/adcp/src/main/java/org/adcontextprotocol/adcp/http/SsrfPolicy.java @@ -0,0 +1,44 @@ +package org.adcontextprotocol.adcp.http; + +import java.net.InetAddress; + +/** + * SSRF policy for outbound discovery probes. + * + *

See {@code specs/ssrf-baseline.md} for the threat model and the full + * block-list. The short version: every outbound HTTP probe resolves DNS once, + * runs every resolved address through {@link #evaluate(InetAddress)}, and + * proceeds only if all addresses return {@link SsrfDecision.Allow}. + * + *

The {@link #strict()} policy is the v0.1 baseline; production builds + * must use it. {@link #permissive()} exists only for local development + * against {@code localhost} and is opt-in per-request, never global. + */ +public sealed interface SsrfPolicy permits StrictSsrfPolicy, PermissiveSsrfPolicy { + + /** + * Evaluate a single resolved address. Implementations should be pure + * functions of the address — DNS resolution belongs in the caller, which + * is responsible for pinning the connect to the validated address. + */ + SsrfDecision evaluate(InetAddress address); + + /** + * The default policy: denies every range in + * {@code specs/ssrf-baseline.md} §"Address guards (block list)". + */ + static SsrfPolicy strict() { + return StrictSsrfPolicy.INSTANCE; + } + + /** + * Permissive policy for local development. Allows every address including + * loopback, link-local (cloud metadata), and RFC 1918 ranges. Documented + * opt-in only — never the default. Never wired via env-var or system + * property so a misconfigured production deploy can't silently disable + * the guard. + */ + static SsrfPolicy permissive() { + return PermissiveSsrfPolicy.INSTANCE; + } +} diff --git a/adcp/src/main/java/org/adcontextprotocol/adcp/http/StrictSsrfPolicy.java b/adcp/src/main/java/org/adcontextprotocol/adcp/http/StrictSsrfPolicy.java new file mode 100644 index 0000000..2529f84 --- /dev/null +++ b/adcp/src/main/java/org/adcontextprotocol/adcp/http/StrictSsrfPolicy.java @@ -0,0 +1,121 @@ +package org.adcontextprotocol.adcp.http; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; + +/** + * Strict SSRF policy — the v0.1 baseline. Denies the address ranges + * listed in {@code specs/ssrf-baseline.md} §"Address guards (block list)". + * + *

Implementation philosophy: walk through {@code InetAddress}'s built-in + * range methods rather than re-rolling CIDR matchers, since the JDK already + * encodes the same RFCs. Anything {@link InetAddress} flags as link-local, + * site-local, loopback, multicast, or "any local" is denied. The + * IPv4-mapped-IPv6 case is handled by extracting the embedded v4 address. + */ +final class StrictSsrfPolicy implements SsrfPolicy { + + static final StrictSsrfPolicy INSTANCE = new StrictSsrfPolicy(); + + private StrictSsrfPolicy() {} + + @Override + public SsrfDecision evaluate(InetAddress address) { + InetAddress effective = unmapIpv4Mapped(address); + + if (effective.isAnyLocalAddress()) { + return new SsrfDecision.Deny("any-local address (0.0.0.0 / ::)"); + } + if (effective.isLoopbackAddress()) { + return new SsrfDecision.Deny("loopback (127.0.0.0/8 or ::1)"); + } + if (effective.isLinkLocalAddress()) { + return new SsrfDecision.Deny("link-local (includes cloud-metadata endpoints)"); + } + if (effective.isSiteLocalAddress()) { + return new SsrfDecision.Deny("RFC 1918 private (10/8, 172.16/12, 192.168/16)"); + } + if (effective.isMulticastAddress()) { + return new SsrfDecision.Deny("multicast (224.0.0.0/4 or ff00::/8)"); + } + if (effective instanceof Inet4Address v4) { + if (isCarrierGradeNat(v4)) { + return new SsrfDecision.Deny("RFC 6598 carrier-grade NAT (100.64/10)"); + } + if (isBenchmark(v4)) { + return new SsrfDecision.Deny("RFC 2544 benchmark (198.18/15)"); + } + if (isIetfProtocolAssignments(v4)) { + return new SsrfDecision.Deny("RFC 6890 IETF protocol assignments (192.0.0/24)"); + } + if (isReservedClassE(v4)) { + return new SsrfDecision.Deny("reserved (240.0.0.0/4)"); + } + } + if (effective instanceof Inet6Address v6 && isIpv6UniqueLocal(v6)) { + return new SsrfDecision.Deny("IPv6 unique local (fc00::/7)"); + } + return SsrfDecision.ALLOW; + } + + private static InetAddress unmapIpv4Mapped(InetAddress address) { + // ::ffff:0:0/96 — an IPv4 address tunneled inside an IPv6 address. + // The JDK's range methods evaluate the v6 form, not the embedded v4, + // so we unwrap to apply the v4 ranges (RFC 1918 etc.) to the + // effective destination. + // + // Note: Inet6Address.isIPv4CompatibleAddress() checks the legacy + // "::a.b.c.d" form (which also matches ::1), not the IPv4-mapped + // "::ffff:a.b.c.d" form we want. We test the bytes directly. + if (!(address instanceof Inet6Address v6)) { + return address; + } + byte[] addr = v6.getAddress(); + // First 80 bits zero, next 16 bits 0xFFFF — the IPv4-mapped form. + for (int i = 0; i < 10; i++) { + if (addr[i] != 0) { + return address; + } + } + if ((addr[10] & 0xFF) != 0xFF || (addr[11] & 0xFF) != 0xFF) { + return address; + } + byte[] v4Bytes = new byte[]{addr[12], addr[13], addr[14], addr[15]}; + try { + return InetAddress.getByAddress(v4Bytes); + } catch (Exception ignored) { + return address; + } + } + + private static boolean isCarrierGradeNat(Inet4Address v4) { + byte[] b = v4.getAddress(); + int first = b[0] & 0xFF; + int second = b[1] & 0xFF; + return first == 100 && second >= 64 && second <= 127; + } + + private static boolean isBenchmark(Inet4Address v4) { + byte[] b = v4.getAddress(); + int first = b[0] & 0xFF; + int second = b[1] & 0xFF; + return first == 198 && (second == 18 || second == 19); + } + + private static boolean isIetfProtocolAssignments(Inet4Address v4) { + byte[] b = v4.getAddress(); + return (b[0] & 0xFF) == 192 && (b[1] & 0xFF) == 0 && (b[2] & 0xFF) == 0; + } + + private static boolean isReservedClassE(Inet4Address v4) { + int first = v4.getAddress()[0] & 0xFF; + return first >= 240; + } + + private static boolean isIpv6UniqueLocal(Inet6Address v6) { + int firstByte = v6.getAddress()[0] & 0xFF; + // fc00::/7 — the first byte is 0xFC or 0xFD. + return firstByte == 0xFC || firstByte == 0xFD; + } +} diff --git a/adcp/src/test/java/org/adcontextprotocol/adcp/http/StrictSsrfPolicyTest.java b/adcp/src/test/java/org/adcontextprotocol/adcp/http/StrictSsrfPolicyTest.java new file mode 100644 index 0000000..d0a1619 --- /dev/null +++ b/adcp/src/test/java/org/adcontextprotocol/adcp/http/StrictSsrfPolicyTest.java @@ -0,0 +1,92 @@ +package org.adcontextprotocol.adcp.http; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Spec tests for {@link StrictSsrfPolicy}. The bar these set is the block + * table in {@code specs/ssrf-baseline.md}; every row gets a parametrized + * literal address denial case. + * + *

DNS-rebinding and redirect-follow cases live on the {@code transport} + * track since they need the live {@code AdcpHttpClient} call path. + */ +class StrictSsrfPolicyTest { + + private final SsrfPolicy policy = SsrfPolicy.strict(); + + @ParameterizedTest(name = "denies {0}") + @ValueSource(strings = { + "0.0.0.0", // any-local + "127.0.0.1", "127.0.0.53", // loopback + "10.0.0.1", "10.255.255.254", // RFC 1918 + "172.16.0.1", "172.31.255.254", // RFC 1918 + "192.168.0.1", "192.168.1.1", // RFC 1918 + "169.254.169.254", // cloud metadata + "169.254.0.1", // link-local + "100.64.0.1", "100.127.255.254", // RFC 6598 CGN + "192.0.0.1", "192.0.0.255", // RFC 6890 IETF + "198.18.0.1", "198.19.255.254", // RFC 2544 benchmark + "224.0.0.1", "239.255.255.254", // multicast + "240.0.0.1", "255.255.255.254", // reserved class E + "::1", // IPv6 loopback + "fe80::1", // IPv6 link-local + "fc00::1", "fd00::1", // IPv6 unique local + "ff02::1", // IPv6 multicast + "::ffff:127.0.0.1", // IPv4-mapped IPv6 loopback + "::ffff:10.0.0.1", // IPv4-mapped IPv6 RFC 1918 + "::ffff:169.254.169.254" // IPv4-mapped IPv6 cloud metadata + }) + void denies_block_table(String literal) throws UnknownHostException { + InetAddress addr = InetAddress.getByName(literal); + SsrfDecision decision = policy.evaluate(addr); + SsrfDecision.Deny deny = assertInstanceOf(SsrfDecision.Deny.class, decision, + () -> literal + " (" + addr + ") should be denied but was " + decision); + // Reason text is a short human-readable string — non-empty so + // operators see something useful in logs. + assertTrue(!deny.reason().isBlank(), "deny reason should not be blank"); + } + + @ParameterizedTest(name = "allows {0}") + @ValueSource(strings = { + "8.8.8.8", // Google public DNS + "1.1.1.1", // Cloudflare public DNS + "93.184.216.34", // example.com (stable for the test) + "2606:4700:4700::1111", // Cloudflare public IPv6 + }) + void allows_public(String literal) throws UnknownHostException { + InetAddress addr = InetAddress.getByName(literal); + SsrfDecision decision = policy.evaluate(addr); + assertInstanceOf(SsrfDecision.Allow.class, decision, + () -> literal + " should be allowed but was " + decision); + } + + @Test + @DisplayName("Deny.reason() never contains the rejected address itself (no host-structure leak)") + void deny_reason_does_not_leak_address() throws UnknownHostException { + InetAddress addr = InetAddress.getByName("169.254.169.254"); + SsrfDecision.Deny deny = (SsrfDecision.Deny) policy.evaluate(addr); + assertTrue(!deny.reason().contains("169.254.169.254"), + "deny reason should describe the range, not echo the address: " + deny.reason()); + } + + @Test + void permissive_allows_everything() throws UnknownHostException { + SsrfPolicy permissive = SsrfPolicy.permissive(); + assertEquals(new SsrfDecision.Allow(), + permissive.evaluate(InetAddress.getByName("127.0.0.1"))); + assertEquals(new SsrfDecision.Allow(), + permissive.evaluate(InetAddress.getByName("169.254.169.254"))); + assertEquals(new SsrfDecision.Allow(), + permissive.evaluate(InetAddress.getByName("10.0.0.1"))); + } +} diff --git a/build-logic/src/main/kotlin/adcp.java-library-conventions.gradle.kts b/build-logic/src/main/kotlin/adcp.java-library-conventions.gradle.kts index 5ddfe24..437bf99 100644 --- a/build-logic/src/main/kotlin/adcp.java-library-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/adcp.java-library-conventions.gradle.kts @@ -8,6 +8,7 @@ val libs = the() dependencies { "testImplementation"(libs.junit.jupiter.api) + "testImplementation"(libs.junit.jupiter.params) "testRuntimeOnly"(libs.junit.jupiter.engine) "testRuntimeOnly"(libs.junit.platform.launcher) } diff --git a/build-logic/src/main/kotlin/adcp.kotlin-library-conventions.gradle.kts b/build-logic/src/main/kotlin/adcp.kotlin-library-conventions.gradle.kts index 0b0f982..a0de2ba 100644 --- a/build-logic/src/main/kotlin/adcp.kotlin-library-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/adcp.kotlin-library-conventions.gradle.kts @@ -12,6 +12,7 @@ kotlin { dependencies { "testImplementation"(libs.junit.jupiter.api) + "testImplementation"(libs.junit.jupiter.params) "testRuntimeOnly"(libs.junit.jupiter.engine) "testRuntimeOnly"(libs.junit.platform.launcher) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8255437..04d8399 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -54,6 +54,7 @@ jakarta-servlet-api = { module = "jakarta.servlet:jakarta.servlet-api", version. # Test junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" } +junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit" } junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" } junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher" } diff --git a/specs/ssrf-baseline.md b/specs/ssrf-baseline.md new file mode 100644 index 0000000..de60ac9 --- /dev/null +++ b/specs/ssrf-baseline.md @@ -0,0 +1,111 @@ +# SSRF baseline for outbound HTTP + +**Status:** Design spec for the harness `AdcpHttpClient` skeleton +**Tracks:** [`transport`](../ROADMAP.md#track-3--l0-transport-mcp--a2a) (primary owner), [`infra`](../ROADMAP.md#track-1--build-repo-release-infra) (CI gate) +**Decisions referenced:** D2 (JDK 21), D9 (MCP SDK) + +## Why this exists + +The TS SDK shipped a cross-cutting SSRF migration on every outbound discovery probe across the 6.16 → 7.x line ([`detectProtocol`, `discoverAgentProfile`, `fetchAdAgentsJsonFromUrl`, `network-consistency-checker`, the property-crawler, the buyer-side discovery path](https://github.com/adcontextprotocol/adcp-client/releases)). The behavior is now baseline: every probe resolves DNS once, validates the full address set against address-guards, pins the connect to the first validated address, sets `redirect: 'manual'`, and caps the response body at 4 KiB. + +The Java SDK has to match this baseline on every outbound discovery call — **not as a v1.x hardening pass, as v0.1 baseline**. JDK `HttpClient` doesn't pin connect-addresses natively. That's the design problem this spec addresses. + +## Threat model — what we're defending against + +| Class | Example | +|---|---| +| Cloud metadata exfiltration | `http://169.254.169.254/latest/meta-data/iam/security-credentials/` (AWS), `http://metadata.google.internal/computeMetadata/v1/` (GCP), `http://169.254.169.254/metadata/instance` (Azure) | +| RFC 1918 internal exploration | `http://10.0.0.1/admin` | +| Loopback service abuse | `http://localhost:9200/` Elasticsearch, `http://127.0.0.1:8500/v1/agent/services` Consul | +| DNS rebinding | Domain that resolves to a public IP at TXT-record time and to `127.0.0.1` at connect time | +| Redirect-to-internal | Public agent returns `Location: http://10.0.0.1/`; client follows | +| Slow-loris / huge-body | Public agent returns a 10 GB body to exhaust the prober's memory | + +The first four classes target *what we connect to.* The fifth bypasses static URL validation by deferring the attack to redirect-follow time. The sixth weaponizes the prober's resource budget. + +## The four mitigations + +1. **Resolve DNS once, validate the full address set.** For an outbound URL whose host is a name, resolve to the full address list, run every address through the address-guard, accept the call **only if all addresses pass**. Defense-in-depth against partial-block lists (host with one public + one private A record). +2. **Pin the connect to the first validated address.** Bypass `HttpClient`'s implicit re-resolution. Defense against DNS rebinding: even if the name's records change between resolution and connect, we connect to the IP we already validated. +3. **`redirect: 'manual'`.** No transparent redirect-follow. A 3xx from a probe is a result, not an action. Callers that need a follow re-run validation against the new URL. +4. **Body cap.** 4 KiB by default on probes (the agent-card / `tools/list` size class). Configurable per call site. + +## Address guards (block list) + +The default `SsrfPolicy.allowsAddress(InetAddress)` denies: + +| Range | RFC / notes | +|---|---| +| `0.0.0.0/8`, `::/128` | "This network" | +| `10.0.0.0/8` | RFC 1918 private | +| `100.64.0.0/10` | RFC 6598 carrier-grade NAT | +| `127.0.0.0/8`, `::1/128` | Loopback | +| `169.254.0.0/16`, `fe80::/10` | Link-local (includes cloud metadata 169.254.169.254) | +| `172.16.0.0/12` | RFC 1918 private | +| `192.0.0.0/24` | RFC 6890 IETF protocol assignments | +| `192.168.0.0/16` | RFC 1918 private | +| `198.18.0.0/15` | RFC 2544 benchmark | +| `224.0.0.0/4`, `ff00::/8` | Multicast | +| `240.0.0.0/4` | Reserved | +| IPv4-mapped IPv6 (`::ffff:0:0/96`) | Tunneling private IPv4 in IPv6 | +| `fc00::/7` | IPv6 unique local addresses | + +For development against `http://localhost`, callers opt out per-request via `AdcpHttpClient.builder().ssrfPolicy(SsrfPolicy.permissive()).build()` — never via env-var or system property, so a misconfigured production deploy can't silently disable the guard. + +## API shape + +```java +// Spec-level: this is the contract, not the final API. +package org.adcontextprotocol.adcp.http; + +public sealed interface SsrfPolicy { + SsrfDecision evaluate(InetAddress address); + boolean allowsHost(String host); + + static SsrfPolicy strict() { ... } // baseline; denies the table above + static SsrfPolicy permissive() { ... } // local dev only; documented opt-in +} + +public sealed interface SsrfDecision { + record Allow() implements SsrfDecision {} + record Deny(String reason) implements SsrfDecision {} +} + +public final class AdcpHttpClient { + public static Builder builder(); + public HttpResponse send(HttpRequest req, BodySpec body); + // ... +} +``` + +`SsrfPolicy.strict()` is the default; every call site that constructs an `AdcpHttpClient` without specifying a policy gets it. + +## Test contract + +The skeleton ships a JUnit 5 test suite covering: + +- `strict()` denies a literal IP from each entry in the block table +- `strict()` denies a hostname whose A/AAAA records resolve to a denied address +- `strict()` allows a public-looking A record (uses fixture rather than live DNS) +- Redirect to a denied address is rejected (rule 3) +- Body larger than the configured cap is truncated and the truncation is signaled (rule 4) +- The DNS-pin path connects to the resolved address even when the OS resolver returns a different address on a second lookup (rule 2 — DNS-rebinding test) + +The DNS-rebinding test is the hardest; the harness skeleton stubs it out behind `@Disabled("requires DNS-rebinding test harness — see [transport] track")`. Real implementation lands on the [`transport`](../ROADMAP.md#track-3--l0-transport-mcp--a2a) track. + +## What lands in the harness vs. on the `transport` track + +| Harness (this spec) | `transport` track | +|---|---| +| `SsrfPolicy` interface + strict/permissive defaults | Per-call policy override on the HTTP client | +| Address-guard implementation | DNS-pin connect-address binding | +| Body-cap helper | Streaming body cap (current spec is in-memory only) | +| `AdcpHttpClient.builder()` skeleton + a single working `send()` path with redirect:manual | All discovery-probe call sites switched onto `AdcpHttpClient` | +| Tests for address-guard, body cap, redirect | DNS-rebinding integration test | +| Documented opt-out for `localhost` dev | Conformance harness assertion that production builds default to `strict()` | + +## References + +- [RFC 6890 — Special-Purpose IP Address Registries](https://www.rfc-editor.org/rfc/rfc6890) +- [TS SDK PR #1633 — cross-cutting SSRF migration for discovery layer](https://github.com/adcontextprotocol/adcp-client/pull/1633) +- TS SDK release notes 6.16–7.x — see `ROADMAP.md` §7.x deltas From fdfd9f1a80515a1b1412d492045d75084a9d6273 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Wed, 13 May 2026 16:44:41 -0400 Subject: [PATCH 08/12] build(infra): schema-bundle fetcher Gradle task (D4 verification path) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds `./gradlew :adcp:fetchSchemaBundle` — downloads the AdCP protocol tarball at the version pinned in ADCP_VERSION (or via -PadcpVersion=X.Y.Z), verifies SHA-256 and Sigstore signatures per https://github.com/adcontextprotocol/adcp/blob/main/docs/reference/verifying-protocol-tarballs.mdx, and extracts to build/schemas/. The Sigstore verification matches the spec doc's invocation exactly: - Identity regex pinned to release.yml@refs/(heads|tags)/.* on the adcp repo (matches the maintenance branches 3.0.x, 2.6.x; forward-compat for tag-triggered re-signing) - OIDC issuer pinned to GitHub Actions' token endpoint - cosign verify-blob fails the task if anything other than the AdCP release workflow produced the signature Sidecars (.sha256, .sig, .crt) are confirmed live for versioned releases on https://adcontextprotocol.org/protocol/{version}.* — verified end-to-end locally against 3.0.11 with the local cosign binary. ADCP_VERSION at the repo root pins 3.0.11 (current latest at time of import). Same shape the TS SDK uses for its pin file. Bumping it is a deliberate PR — D15 cadence (≤2 weeks spec-rev tracking). -PskipCosign=true is a documented offline-dev escape hatch only; logs a warning and is never silent. The convention is registered as adcp.schema-bundle-conventions and applied to the adcp module today; other modules can pick it up when they need raw schema access. CI doesn't yet install cosign — that lands when the codegen track wires fetchSchemaBundle as a dependency of compile. Today the task runs only when explicitly invoked. Co-Authored-By: Claude Opus 4.7 (1M context) --- ADCP_VERSION | 1 + adcp/build.gradle.kts | 1 + .../adcp.schema-bundle-conventions.gradle.kts | 151 ++++++++++++++++++ 3 files changed, 153 insertions(+) create mode 100644 ADCP_VERSION create mode 100644 build-logic/src/main/kotlin/adcp.schema-bundle-conventions.gradle.kts diff --git a/ADCP_VERSION b/ADCP_VERSION new file mode 100644 index 0000000..778bf95 --- /dev/null +++ b/ADCP_VERSION @@ -0,0 +1 @@ +3.0.11 diff --git a/adcp/build.gradle.kts b/adcp/build.gradle.kts index 84c15e3..3cd808f 100644 --- a/adcp/build.gradle.kts +++ b/adcp/build.gradle.kts @@ -4,6 +4,7 @@ plugins { id("adcp.java-library-conventions") + id("adcp.schema-bundle-conventions") } description = "AdCP Java SDK — caller, types, schema bundle" diff --git a/build-logic/src/main/kotlin/adcp.schema-bundle-conventions.gradle.kts b/build-logic/src/main/kotlin/adcp.schema-bundle-conventions.gradle.kts new file mode 100644 index 0000000..4af66b6 --- /dev/null +++ b/build-logic/src/main/kotlin/adcp.schema-bundle-conventions.gradle.kts @@ -0,0 +1,151 @@ +// Schema-bundle fetcher (per D4 — Sigstore-signed tarball at +// https://adcontextprotocol.org/protocol/{version}.tgz with .sig + .crt +// sidecars). Applied to the adcp module; the codegen track depends on +// this output landing under build/schemas/. +// +// The verification follows +// https://github.com/adcontextprotocol/adcp/blob/main/docs/reference/verifying-protocol-tarballs.mdx +// — cosign verify-blob against the AdCP release workflow identity. The +// SHA-256 sidecar is also fetched for in-transit integrity. +// +// `cosign` is expected on PATH. CI installs it via +// sigstore/cosign-installer@v3. + +import java.security.MessageDigest + +abstract class FetchSchemaBundle : DefaultTask() { + + @get:org.gradle.api.tasks.Input + abstract val adcpVersion: org.gradle.api.provider.Property + + @get:org.gradle.api.tasks.Input + @get:org.gradle.api.tasks.Optional + abstract val baseUrl: org.gradle.api.provider.Property + + @get:org.gradle.api.tasks.OutputDirectory + abstract val outputDir: org.gradle.api.file.DirectoryProperty + + @get:org.gradle.api.tasks.Input + @get:org.gradle.api.tasks.Optional + abstract val skipCosign: org.gradle.api.provider.Property + + @org.gradle.api.tasks.TaskAction + fun fetch() { + val version = adcpVersion.get() + val base = baseUrl.getOrElse("https://adcontextprotocol.org/protocol") + val out = outputDir.get().asFile + out.deleteRecursively() + out.mkdirs() + + val tarball = java.io.File(out, "$version.tgz") + val sha256 = java.io.File(out, "$version.tgz.sha256") + val sig = java.io.File(out, "$version.tgz.sig") + val crt = java.io.File(out, "$version.tgz.crt") + + download("$base/$version.tgz", tarball) + download("$base/$version.tgz.sha256", sha256) + download("$base/$version.tgz.sig", sig) + download("$base/$version.tgz.crt", crt) + + verifySha256(tarball, sha256) + if (skipCosign.getOrElse(false)) { + logger.warn( + "Skipping cosign verification of $version.tgz (skipCosign=true). " + + "Never skip on release builds — D4 requires Sigstore verification." + ) + } else { + verifyCosign(tarball, sig, crt) + } + + extract(tarball, out) + logger.lifecycle("Schema bundle $version extracted to ${out.absolutePath}") + } + + private fun download(url: String, target: java.io.File) { + logger.info("Downloading $url") + java.net.URI.create(url).toURL().openStream().use { input -> + target.outputStream().use { output -> input.copyTo(output) } + } + } + + private fun verifySha256(tarball: java.io.File, shaFile: java.io.File) { + // Format: " " on a single line. + val expected = shaFile.readText().trim().substringBefore(' ').lowercase() + val actual = MessageDigest.getInstance("SHA-256").let { md -> + tarball.inputStream().use { input -> + val buf = ByteArray(8192) + while (true) { + val read = input.read(buf) + if (read <= 0) break + md.update(buf, 0, read) + } + } + md.digest().joinToString("") { "%02x".format(it) } + } + check(expected == actual) { + "SHA-256 mismatch on $tarball: expected $expected, got $actual" + } + logger.info("SHA-256 verified: $actual") + } + + private fun verifyCosign(tarball: java.io.File, sig: java.io.File, crt: java.io.File) { + // Identity regex from the spec doc: + // ^https://github.com/adcontextprotocol/adcp/.github/workflows/release.yml@refs/(heads|tags)/.*$ + val identityRegex = + "^https://github\\.com/adcontextprotocol/adcp/\\.github/workflows/release\\.yml@refs/(heads|tags)/.*$" + + val process = ProcessBuilder( + "cosign", "verify-blob", + "--signature", sig.absolutePath, + "--certificate", crt.absolutePath, + "--certificate-identity-regexp", identityRegex, + "--certificate-oidc-issuer", "https://token.actions.githubusercontent.com", + tarball.absolutePath + ) + .redirectErrorStream(true) + .start() + val output = process.inputStream.bufferedReader().readText() + val exitCode = process.waitFor() + check(exitCode == 0) { + "cosign verify-blob failed (exit $exitCode):\n$output" + } + logger.info("Sigstore verification OK") + } + + private fun extract(tarball: java.io.File, dest: java.io.File) { + // Plain JDK extraction — avoid pulling in a tar library when the + // tooling is one external invocation. Mirrors how the TS SDK's + // download.sh uses tar. + val process = ProcessBuilder("tar", "xzf", tarball.absolutePath, "-C", dest.absolutePath) + .redirectErrorStream(true) + .start() + val output = process.inputStream.bufferedReader().readText() + val exitCode = process.waitFor() + check(exitCode == 0) { + "tar extraction failed (exit $exitCode):\n$output" + } + } +} + +// Register a top-level `fetchSchemaBundle` task on every module that applies +// this convention. Today the adcp module is the consumer; if other modules +// need raw schema access later they can depend on adcp's output directory. +tasks.register("fetchSchemaBundle") { + description = "Downloads + Sigstore-verifies the AdCP protocol tarball (per D4)." + group = "build setup" + + // Default to whatever is pinned in ADCP_VERSION (root-level file, same + // shape the TS SDK uses). Override per-call with -PadcpVersion=X.Y.Z. + val pinned = project.findProperty("adcpVersion") as String? + ?: project.rootProject.file("ADCP_VERSION").takeIf { it.exists() }?.readText()?.trim() + ?: error( + "No AdCP version pinned. Set -PadcpVersion=X.Y.Z or create " + + "ADCP_VERSION at the repo root." + ) + adcpVersion = pinned + outputDir = project.layout.buildDirectory.dir("schemas") + + // Override with -PskipCosign=true only for offline development. Never + // for release builds. + skipCosign = (project.findProperty("skipCosign") as String?)?.toBoolean() ?: false +} From 9ee81dde2f5cce9fdec10e5116be8bfcdc6576cd Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Wed, 13 May 2026 16:45:50 -0400 Subject: [PATCH 09/12] ci(storyboard): mock-server sidecar workflow + reachability test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First piece of the D8 storyboard CI gate. Adds: - .github/workflows/storyboard.yml — boots `npx adcp mock-server sales-guaranteed --port 4500` as a sidecar process before running ./gradlew :adcp-testing:test. Waits up to 30s for the sidecar to respond on /, captures logs on failure, kills the PID on cleanup. - adcp-testing/src/test/.../MockServerReachableTest.java — JUnit 5 test gated on the ADCP_MOCK_SERVER_URL env var. Asserts the sidecar is reachable and responds <500. Locally the test is skipped (no env var); CI sets it via the workflow step. As the testing track lands StoryboardRunner, the runner replaces this one-line assertion while the workflow shape stays unchanged. - package.json + package-lock.json: pin @adcp/sdk 7.2.0 as a devDep so `npm ci` brings the `adcp` CLI into the workflow's PATH. The pinned version is the conformance oracle — bumping it is a deliberate PR per D8. The other six specialisms (signal-marketplace, sales-non-guaranteed, sales-social, creative-template, sponsored-intelligence, creative-ad-server) come online as the testing track adds storyboards that exercise them; the workflow shape extends to a port-range pattern (4500–4506) at that point. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/storyboard.yml | 88 + .../adcp/testing/MockServerReachableTest.java | 56 + package-lock.json | 1428 ++++++++++++++++- package.json | 1 + 4 files changed, 1546 insertions(+), 27 deletions(-) create mode 100644 .github/workflows/storyboard.yml create mode 100644 adcp-testing/src/test/java/org/adcontextprotocol/adcp/testing/MockServerReachableTest.java diff --git a/.github/workflows/storyboard.yml b/.github/workflows/storyboard.yml new file mode 100644 index 0000000..b4233c0 --- /dev/null +++ b/.github/workflows/storyboard.yml @@ -0,0 +1,88 @@ +name: storyboard + +# Storyboard CI gate (per D8). Boots `npx adcp mock-server ` +# as a sidecar process for each AdCP-defined specialism, then runs the +# storyboard tests against them. +# +# Today this is the gate shell — there's one MockServerReachableTest in +# adcp-testing that proves the sidecar booted. As the `testing` track +# lands the real StoryboardRunner, that runner replaces this assertion +# while the workflow shape stays unchanged. +# +# The pinned @adcp/sdk version in package-lock.json becomes the +# conformance oracle — bumping it is a deliberate PR (D8). + +on: + pull_request: + push: + branches: [main] + +permissions: + contents: read + +jobs: + storyboard: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 + with: + validate-wrappers: true + + - name: Set up Node.js for mock-server sidecar + uses: actions/setup-node@v5 + with: + node-version: '20.x' + cache: 'npm' + + - name: Install @adcp/sdk + # The pinned version is what conformance is graded against. CI + # locks the version through package-lock.json (npm ci is + # reproducible). Bumping the pin is a deliberate PR. + run: npm ci + + - name: Boot mock-server (sales-guaranteed specialism) + # One specialism is enough for the gate shell. The testing track + # adds the other specialisms (signal-marketplace, sales-social, + # creative-template, sponsored-intelligence, sales-non-guaranteed, + # creative-ad-server) on the port range starting at 4500. + run: | + npx adcp mock-server sales-guaranteed --port 4500 \ + > mock-server.log 2>&1 & + echo $! > mock-server.pid + # Wait for it to come up (up to 30s). + for i in $(seq 1 30); do + if curl -sf http://localhost:4500/ -o /dev/null 2>&1; then + echo "mock-server up after ${i}s"; break + fi + sleep 1 + done + + - name: Run storyboard tests + env: + ADCP_MOCK_SERVER_URL: http://localhost:4500 + run: ./gradlew :adcp-testing:test --info + + - name: Capture mock-server logs on failure + if: always() + run: | + if [ -f mock-server.log ]; then + echo "::group::mock-server.log" + cat mock-server.log + echo "::endgroup::" + fi + + - name: Stop mock-server + if: always() + run: | + if [ -f mock-server.pid ]; then + kill "$(cat mock-server.pid)" || true + fi diff --git a/adcp-testing/src/test/java/org/adcontextprotocol/adcp/testing/MockServerReachableTest.java b/adcp-testing/src/test/java/org/adcontextprotocol/adcp/testing/MockServerReachableTest.java new file mode 100644 index 0000000..4d8760d --- /dev/null +++ b/adcp-testing/src/test/java/org/adcontextprotocol/adcp/testing/MockServerReachableTest.java @@ -0,0 +1,56 @@ +package org.adcontextprotocol.adcp.testing; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Storyboard CI gate shell (per D8). + * + *

Asserts the Java SDK can reach the {@code @adcp/sdk/mock-server} sidecar + * that CI boots before running tests. Today this is the only assertion; + * as the {@code testing} track lands, this test grows into the real + * storyboard runner driving {@code StoryboardRunner.run(...)}. + * + *

Skipped when {@code ADCP_MOCK_SERVER_URL} is unset (i.e. when running + * tests locally without the sidecar). CI sets it via the workflow's + * service-container step. + */ +@EnabledIfEnvironmentVariable( + named = "ADCP_MOCK_SERVER_URL", + matches = ".+", + disabledReason = "Set ADCP_MOCK_SERVER_URL (e.g. http://localhost:4500) to run; CI sets it automatically" +) +class MockServerReachableTest { + + @Test + void mock_server_is_reachable() throws Exception { + String base = System.getenv("ADCP_MOCK_SERVER_URL"); + assertNotNull(base, "ADCP_MOCK_SERVER_URL not set"); + + HttpClient client = HttpClient.newBuilder() + .connectTimeout(Duration.ofSeconds(5)) + .build(); + + // The mock-server exposes a health/root path; we don't pin the + // path shape yet (it evolves with @adcp/sdk versions). Any + // 2xx/3xx/4xx response proves the server is up. + HttpRequest req = HttpRequest.newBuilder() + .GET() + .uri(URI.create(base + "/")) + .timeout(Duration.ofSeconds(5)) + .build(); + + HttpResponse resp = client.send(req, HttpResponse.BodyHandlers.ofString()); + assertTrue(resp.statusCode() < 500, + "expected mock-server to respond <500; got " + resp.statusCode()); + } +} diff --git a/package-lock.json b/package-lock.json index 908abe6..12899b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,12 +6,88 @@ "": { "name": "adcp-sdk-java-tools", "devDependencies": { + "@adcp/sdk": "7.2.0", "@changesets/cli": "^2.29.7", "@commitlint/cli": "^19.6.0", "@commitlint/config-conventional": "^19.6.0", "conventional-changelog-conventionalcommits": "^8.0.0" } }, + "node_modules/@a2a-js/sdk": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@a2a-js/sdk/-/sdk-0.3.13.tgz", + "integrity": "sha512-BZr0f9JVNQs3GKOM9xINWCh6OKIJWZFPyqqVqTym5mxO2Eemc6I/0zL7zWnljHzGdaf5aZQyQN5xa6PSH62q+A==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "uuid": "^11.1.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@bufbuild/protobuf": "^2.10.2", + "@grpc/grpc-js": "^1.11.0", + "express": "^4.21.2 || ^5.1.0" + }, + "peerDependenciesMeta": { + "@bufbuild/protobuf": { + "optional": true + }, + "@grpc/grpc-js": { + "optional": true + }, + "express": { + "optional": true + } + } + }, + "node_modules/@adcp/sdk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@adcp/sdk/-/sdk-7.2.0.tgz", + "integrity": "sha512-pIY1LJZi19df508zoAR954/wPkIM9ZU1GgEvCbYMi/ZAc8FdzbtYDjrQ8osbtzb4wfZBHyxqpbXt5PhIv9yMMg==", + "dev": true, + "license": "Apache-2.0", + "workspaces": [ + ".", + "packages/*" + ], + "dependencies": { + "@types/ws": "^8.18.1", + "ajv": "^8.18.0", + "ajv-formats": "^3.0.1", + "fast-check": "^3.23.2", + "jose": "^6.2.2", + "secure-json-parse": "^4.1.0", + "structured-headers": "^2.0.2", + "tldts": "^7.0.29", + "undici": "^6.25.0", + "ws": "^8.20.0", + "yaml": "^2.7.1" + }, + "bin": { + "adcp": "bin/adcp.js" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@a2a-js/sdk": "^0.3.4", + "@modelcontextprotocol/sdk": "^1.17.5", + "@opentelemetry/api": "^1.0.0", + "pg": "^8.0.0", + "zod": "^4.1.5" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "pg": { + "optional": true + } + } + }, "node_modules/@babel/code-frame": { "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", @@ -559,6 +635,20 @@ "node": ">=v18" } }, + "node_modules/@hono/node-server": { + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, "node_modules/@inquirer/external-editor": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", @@ -719,6 +809,48 @@ "node": ">=6 <7 || >=8" } }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -777,6 +909,31 @@ "undici-types": "~7.21.0" } }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/ajv": { "version": "8.20.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", @@ -794,6 +951,24 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -867,6 +1042,32 @@ "node": ">=4" } }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/braces": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", @@ -880,6 +1081,50 @@ "node": ">=8" } }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -956,6 +1201,32 @@ "dot-prop": "^5.1.0" } }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/conventional-changelog-angular": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz", @@ -1001,6 +1272,47 @@ "node": ">=16" } }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/cosmiconfig": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz", @@ -1074,6 +1386,36 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/detect-indent": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", @@ -1110,6 +1452,30 @@ "node": ">=8" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -1117,6 +1483,17 @@ "dev": true, "license": "MIT" }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/enquirer": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", @@ -1151,6 +1528,42 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -1161,6 +1574,14 @@ "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -1175,22 +1596,146 @@ "node": ">=4" } }, - "node_modules/extendable-error": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/extendable-error/-/extendable-error-0.1.7.tgz", - "integrity": "sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==", + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", "dev": true, - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.3", + "license": "MIT", + "peer": true, + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz", + "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.1.tgz", + "integrity": "sha512-5O6KYmyJEpuPJV5hNTXKbAHWRqrzyu+OI3vUnSd2kXFubIVpG7ezpgxQy76Zo5GQZtrQBg86hF+CM/NX+cioiQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ip-address": "^10.2.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/extendable-error": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/extendable-error/-/extendable-error-0.1.7.tgz", + "integrity": "sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-check": { + "version": "3.23.2", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", + "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "dependencies": { + "pure-rand": "^6.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, @@ -1246,6 +1791,29 @@ "node": ">=8" } }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/find-up": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-7.0.0.tgz", @@ -1264,6 +1832,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/fs-extra": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", @@ -1279,6 +1869,17 @@ "node": ">=6 <7 || >=8" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -1289,6 +1890,47 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/git-raw-commits": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-4.0.0.tgz", @@ -1358,6 +2000,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -1365,6 +2021,67 @@ "dev": true, "license": "ISC" }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hono": { + "version": "4.12.18", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.18.tgz", + "integrity": "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/human-id": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/human-id/-/human-id-4.1.3.tgz", @@ -1440,6 +2157,14 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC", + "peer": true + }, "node_modules/ini": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", @@ -1450,6 +2175,28 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/ip-address": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -1510,6 +2257,14 @@ "node": ">=8" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/is-subdir": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-subdir/-/is-subdir-1.2.0.tgz", @@ -1563,6 +2318,16 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/jose": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -1597,6 +2362,14 @@ "dev": true, "license": "MIT" }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true + }, "node_modules/jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -1720,6 +2493,28 @@ "dev": true, "license": "MIT" }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/meow": { "version": "12.1.1", "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", @@ -1733,6 +2528,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -1757,6 +2566,35 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -1777,27 +2615,96 @@ "node": ">=4" } }, - "node_modules/outdent": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/outdent/-/outdent-0.5.0.tgz", - "integrity": "sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==", + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, - "node_modules/p-filter": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz", - "integrity": "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==", + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "dev": true, "license": "MIT", - "dependencies": { - "p-map": "^2.0.0" - }, + "peer": true, "engines": { - "node": ">=8" + "node": ">= 0.6" } }, - "node_modules/p-limit": { + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/outdent": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/outdent/-/outdent-0.5.0.tgz", + "integrity": "sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/p-filter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz", + "integrity": "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-map": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", @@ -1891,6 +2798,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/path-exists": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", @@ -1911,6 +2829,18 @@ "node": ">=8" } }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -1951,6 +2881,17 @@ "node": ">=6" } }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16.20.0" + } + }, "node_modules/prettier": { "version": "2.8.8", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", @@ -1967,6 +2908,55 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/quansync": { "version": "0.2.11", "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", @@ -2005,6 +2995,34 @@ ], "license": "MIT" }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/read-yaml-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-yaml-file/-/read-yaml-file-1.1.0.tgz", @@ -2086,6 +3104,24 @@ "node": ">=0.10.0" } }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -2117,6 +3153,23 @@ "dev": true, "license": "MIT" }, + "node_modules/secure-json-parse": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz", + "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/semver": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", @@ -2130,6 +3183,63 @@ "node": ">=10" } }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC", + "peer": true + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -2153,6 +3263,86 @@ "node": ">=8" } }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -2204,6 +3394,17 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -2242,6 +3443,17 @@ "node": ">=4" } }, + "node_modules/structured-headers": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/structured-headers/-/structured-headers-2.0.2.tgz", + "integrity": "sha512-IUul56vVHuMg2UxWhwDj9zVJE6ztYEQQkynr1FQ/NydPhivtk5+Qb2N1RS36owEFk2fNUriTguJ2R7htRObcdA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18", + "npm": ">=6" + } + }, "node_modules/term-size": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", @@ -2285,6 +3497,26 @@ "node": ">=18" } }, + "node_modules/tldts": { + "version": "7.0.30", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.30.tgz", + "integrity": "sha512-ELrFxuqsDdHUwoh0XxDbxuLD3Wnz49Z57IFvTtvWy1hJdcMZjXLIuonjilCiWHlT2GbE4Wlv1wKVTzDFnXH1aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.30" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.30", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.30.tgz", + "integrity": "sha512-uiHN8PIB1VmWyS98eZYja4xzlYqeFZVjb4OuYlJQnZAuJhMw4PbKQOKgHKhBdJR3FE/t5mUQ1Kd80++B+qhD1Q==", + "dev": true, + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -2298,6 +3530,33 @@ "node": ">=8.0" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/typescript": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", @@ -2313,6 +3572,16 @@ "node": ">=14.17" } }, + "node_modules/undici": { + "version": "6.25.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.25.0.tgz", + "integrity": "sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, "node_modules/undici-types": { "version": "7.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.21.0.tgz", @@ -2343,6 +3612,43 @@ "node": ">= 4.0.0" } }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uuid": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "peer": true, + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -2377,6 +3683,36 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/ws": { + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", + "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -2387,6 +3723,22 @@ "node": ">=10" } }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -2428,6 +3780,28 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "dev": true, + "license": "ISC", + "peer": true, + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } } } } diff --git a/package.json b/package.json index 4cc4f93..ff327be 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "commitlint": "commitlint" }, "devDependencies": { + "@adcp/sdk": "7.2.0", "@changesets/cli": "^2.29.7", "@commitlint/cli": "^19.6.0", "@commitlint/config-conventional": "^19.6.0", From f6b470b8247a43bc960de5838b5ab3ccd83825c4 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Wed, 13 May 2026 16:51:03 -0400 Subject: [PATCH 10/12] =?UTF-8?q?feat(codegen):=20MVP=20=E2=80=94=20genera?= =?UTF-8?q?te=20records=20from=20JSON=20Schemas=20(PaginationRequest/Respo?= =?UTF-8?q?nse)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First piece of the codegen track. Proves the architecture; the full surface lands on Track 2 (L0 types & codegen). What it does: - JavaPoet-based generator (build-logic/src/main/kotlin/codegen/SchemaCodegen.kt) reads a JSON Schema file and emits one Java record. - *Request types get a static `builder()` factory + a nested `Builder` class (per RFC §Type generation: *Request always has a builder). - *Response types are records only, no builder. - Required fields are non-null components; optional fields get @org.jspecify.annotations.Nullable. - Snake_case schema names map to camelCase Java via @JsonProperty — the wire format never sees camelCase. - JavaDoc generated from the schema's description + per-field descriptions as @param tags. - @javax.annotation.processing.Generated annotation cites the source schema file. MVP scope (2 schemas, both flat scalar): - core/pagination-request.json → PaginationRequest - core/pagination-response.json → PaginationResponse Wired as a Gradle task `:adcp:generateSchemas` that depends on `fetchSchemaBundle` (per D4). Output lands in adcp/build/generated/sources/codegen/main/java/ and is added to the main source set so compileJava picks it up. Not checked in — regenerated from the pinned ADCP_VERSION every build. Round-trip smoke test in adcp/src/test/.../GeneratedPaginationTest.java covers: - Builder produces a record that serializes snake_case - camelCase Java names never leak to the wire - Required field deserializes when present, nulls for optional - Symmetric round-trip with all optional fields populated - Unset optional fields default to null 4 codegen tests + 35 SSRF tests all green. 39 tests total in :adcp. CI gets cosign installed via sigstore/cosign-installer@v3 in both ci.yml and storyboard.yml so fetchSchemaBundle (and now compileJava) works end-to-end on GitHub Actions. Polymorphism (oneOf with discriminator), $ref resolution, nested objects, arrays of complex types, format-asset-slot unions (per 7.x deltas), and the rest of the generator surface — Track 2. .gitignore picks up .local/ — coding-agent state side channel. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 6 + .github/workflows/storyboard.yml | 3 + .gitignore | 1 + adcp/build.gradle.kts | 3 +- .../generated/GeneratedPaginationTest.java | 87 ++++++++ build-logic/build.gradle.kts | 6 + ...adcp.schema-codegen-conventions.gradle.kts | 82 ++++++++ .../src/main/kotlin/codegen/SchemaCodegen.kt | 196 ++++++++++++++++++ gradle/libs.versions.toml | 6 + 9 files changed, 388 insertions(+), 2 deletions(-) create mode 100644 adcp/src/test/java/org/adcontextprotocol/adcp/generated/GeneratedPaginationTest.java create mode 100644 build-logic/src/main/kotlin/adcp.schema-codegen-conventions.gradle.kts create mode 100644 build-logic/src/main/kotlin/codegen/SchemaCodegen.kt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0dda1c5..c1f81e5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,5 +28,11 @@ jobs: # — the standard supply-chain check for OSS Java repos. validate-wrappers: true + - name: Install cosign + # Schema-bundle fetcher (per D4) shells out to `cosign verify-blob` + # against the AdCP release workflow's Sigstore signature. Building + # the adcp module triggers fetchSchemaBundle → cosign. + uses: sigstore/cosign-installer@v3 + - name: Build all modules run: ./gradlew build --stacktrace diff --git a/.github/workflows/storyboard.yml b/.github/workflows/storyboard.yml index b4233c0..a91c83e 100644 --- a/.github/workflows/storyboard.yml +++ b/.github/workflows/storyboard.yml @@ -37,6 +37,9 @@ jobs: with: validate-wrappers: true + - name: Install cosign + uses: sigstore/cosign-installer@v3 + - name: Set up Node.js for mock-server sidecar uses: actions/setup-node@v5 with: diff --git a/.gitignore b/.gitignore index 2c9dacd..03c9122 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ npm-debug.log* # Coding-agent side channels (Conductor workspace state, etc.) .claude/ .context/ +.local/ # OS .DS_Store diff --git a/adcp/build.gradle.kts b/adcp/build.gradle.kts index 3cd808f..8a1ae54 100644 --- a/adcp/build.gradle.kts +++ b/adcp/build.gradle.kts @@ -3,8 +3,7 @@ // `/types`, `/types/v2-5`, `/auth`, `/advanced`, `/schemas`. plugins { - id("adcp.java-library-conventions") - id("adcp.schema-bundle-conventions") + id("adcp.schema-codegen-conventions") } description = "AdCP Java SDK — caller, types, schema bundle" diff --git a/adcp/src/test/java/org/adcontextprotocol/adcp/generated/GeneratedPaginationTest.java b/adcp/src/test/java/org/adcontextprotocol/adcp/generated/GeneratedPaginationTest.java new file mode 100644 index 0000000..c5765ab --- /dev/null +++ b/adcp/src/test/java/org/adcontextprotocol/adcp/generated/GeneratedPaginationTest.java @@ -0,0 +1,87 @@ +package org.adcontextprotocol.adcp.generated; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.adcontextprotocol.adcp.generated.core.PaginationRequest; +import org.adcontextprotocol.adcp.generated.core.PaginationResponse; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Smoke test for the codegen MVP. Validates that generated records: + *

    + *
  • Build via the static {@code builder()} factory (Request only).
  • + *
  • Round-trip through Jackson with snake_case JSON names.
  • + *
  • Treat required fields as non-null and optional as nullable.
  • + *
+ * + *

The full codegen track adds the rest of the surface; this MVP exists + * to prove the architecture. + */ +class GeneratedPaginationTest { + + private final ObjectMapper mapper = new ObjectMapper(); + + @Test + void request_round_trips_with_snake_case_wire_format() throws Exception { + PaginationRequest req = PaginationRequest.builder() + .maxResults(25) + .cursor("abc123") + .build(); + + String json = mapper.writeValueAsString(req); + JsonNode parsed = mapper.readTree(json); + + // Wire format must use the snake_case schema names, not the + // camelCase Java component names. + assertEquals(25, parsed.get("max_results").asInt()); + assertEquals("abc123", parsed.get("cursor").asText()); + assertFalse(parsed.has("maxResults"), "camelCase leaked into wire: " + json); + + // Deserialize back; values match. + PaginationRequest decoded = mapper.readValue(json, PaginationRequest.class); + assertEquals(req, decoded); + } + + @Test + void response_required_field_round_trips() throws Exception { + // has_more is the only required field. + String json = "{\"has_more\": true}"; + PaginationResponse resp = mapper.readValue(json, PaginationResponse.class); + assertTrue(resp.hasMore()); + assertNull(resp.cursor()); + assertNull(resp.totalCount()); + } + + @Test + void response_full_round_trips() throws Exception { + String json = "{\"has_more\": true, \"cursor\": \"xyz\", \"total_count\": 142}"; + PaginationResponse resp = mapper.readValue(json, PaginationResponse.class); + assertTrue(resp.hasMore()); + assertEquals("xyz", resp.cursor()); + assertEquals(142, resp.totalCount()); + + String reserialized = mapper.writeValueAsString(resp); + JsonNode parsed = mapper.readTree(reserialized); + assertEquals(142, parsed.get("total_count").asInt()); + assertTrue(parsed.get("has_more").asBoolean()); + } + + @Test + void request_omits_unset_optional_fields() throws Exception { + // Builder unset → null → Jackson should still emit (Jackson's default + // is to write nulls). Default ObjectMapper config is fine for the + // MVP. The codegen track will tighten serialization config when the + // full ObjectMapper integration lands. + PaginationRequest req = PaginationRequest.builder().maxResults(10).build(); + String json = mapper.writeValueAsString(req); + JsonNode parsed = mapper.readTree(json); + assertEquals(10, parsed.get("max_results").asInt()); + assertTrue(parsed.has("cursor"), "default Jackson config writes nulls"); + assertTrue(parsed.get("cursor").isNull()); + } +} diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index ebb94b4..e8fb51c 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -12,4 +12,10 @@ dependencies { implementation(libs.plugins.kotlin.jvm.map { "org.jetbrains.kotlin:kotlin-gradle-plugin:${it.version}" }) + + // Used by the codegen task to emit Java source from JSON Schemas. + // Build-time only; never on the SDK's runtime classpath. + implementation(libs.javapoet) + implementation(libs.jackson.databind) + implementation(libs.jspecify) } diff --git a/build-logic/src/main/kotlin/adcp.schema-codegen-conventions.gradle.kts b/build-logic/src/main/kotlin/adcp.schema-codegen-conventions.gradle.kts new file mode 100644 index 0000000..d521a9d --- /dev/null +++ b/build-logic/src/main/kotlin/adcp.schema-codegen-conventions.gradle.kts @@ -0,0 +1,82 @@ +// Schema codegen wiring (per Track 2 / harness MVP). Generates Java +// records from the extracted JSON Schemas. Today the MVP runs against +// one pair (PaginationRequest / PaginationResponse from +// schemas/core/) to prove the architecture; full coverage lands on the +// codegen track. + +import codegen.SchemaCodegen + +plugins { + id("adcp.java-library-conventions") + id("adcp.schema-bundle-conventions") +} + +abstract class GenerateSchemas : DefaultTask() { + + @get:org.gradle.api.tasks.InputDirectory + abstract val schemaRoot: org.gradle.api.file.DirectoryProperty + + @get:org.gradle.api.tasks.Input + abstract val basePackage: org.gradle.api.provider.Property + + @get:org.gradle.api.tasks.Input + abstract val schemaFiles: org.gradle.api.provider.ListProperty + + @get:org.gradle.api.tasks.OutputDirectory + abstract val outputDir: org.gradle.api.file.DirectoryProperty + + @org.gradle.api.tasks.TaskAction + fun generate() { + val codegen = SchemaCodegen(basePackage.get()) + val out = outputDir.get().asFile + out.deleteRecursively() + out.mkdirs() + val root = schemaRoot.get().asFile + schemaFiles.get().forEach { relativePath -> + val schemaFile = root.resolve(relativePath) + check(schemaFile.exists()) { + "Schema file not found: $schemaFile (relativePath=$relativePath, root=$root)" + } + val generated = codegen.generate(schemaFile, out) + logger.lifecycle("Generated: ${out.toPath().relativize(generated)}") + } + } +} + +val generateTask = tasks.register("generateSchemas") { + description = "Generate Java records from a subset of AdCP JSON Schemas (MVP)." + group = "build setup" + dependsOn("fetchSchemaBundle") + + val adcpVersion = project.rootProject.file("ADCP_VERSION").readText().trim() + schemaRoot.set( + project.layout.buildDirectory.dir("schemas/adcp-$adcpVersion/schemas") + ) + basePackage.set("org.adcontextprotocol.adcp.generated") + schemaFiles.set( + listOf( + "core/pagination-request.json", + "core/pagination-response.json" + ) + ) + outputDir.set(project.layout.buildDirectory.dir("generated/sources/codegen/main/java")) +} + +// Wire generated sources into the main source set so compileJava picks +// them up. +extensions.configure("sourceSets") { + named("main") { + java.srcDir(generateTask.map { it.outputDir }) + } +} + +tasks.named("compileJava") { + dependsOn(generateTask) +} + +// Jackson annotations referenced by the generated classes need to be on +// the compile classpath of the consuming module. +val libs = the() +dependencies { + "implementation"(libs.jackson.databind) +} diff --git a/build-logic/src/main/kotlin/codegen/SchemaCodegen.kt b/build-logic/src/main/kotlin/codegen/SchemaCodegen.kt new file mode 100644 index 0000000..4b893ea --- /dev/null +++ b/build-logic/src/main/kotlin/codegen/SchemaCodegen.kt @@ -0,0 +1,196 @@ +package codegen + +import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.databind.ObjectMapper +import com.palantir.javapoet.AnnotationSpec +import com.palantir.javapoet.ClassName +import com.palantir.javapoet.FieldSpec +import com.palantir.javapoet.JavaFile +import com.palantir.javapoet.MethodSpec +import com.palantir.javapoet.ParameterSpec +import com.palantir.javapoet.ParameterizedTypeName +import com.palantir.javapoet.TypeName +import com.palantir.javapoet.TypeSpec +import org.jspecify.annotations.Nullable +import java.io.File +import java.nio.file.Path +import javax.lang.model.element.Modifier + +/** + * Schema codegen — MVP scope (per the harness §Pre-contributor table in + * ROADMAP). Generates one record per JSON Schema file. Two output shapes: + * + * - `*Request` types → record + nested `Builder` ([D2] / RFC §Type + * generation: `*Request` types always have builders). + * - `*Response` types → record only, no builder. + * + * The MVP supports flat objects with scalar fields (string, integer, + * boolean) and `additionalProperties: false`. Polymorphism (`oneOf` with + * `discriminator`), arrays, nested objects, and refs to other schemas + * land on the codegen track ([Track 2 — L0 types & codegen](../../../../../../ROADMAP.md#track-2--l0-types--codegen)). + */ +class SchemaCodegen(private val basePackage: String) { + + private val mapper = ObjectMapper() + + /** + * Generates a Java record from one schema file. Writes the result to + * `outputDir//.java`. Returns the generated + * file path. + */ + fun generate(schemaFile: File, outputDir: File): Path { + val schema = mapper.readTree(schemaFile) + val title = schema.required("title").asText() + val className = toClassName(title) + val isRequest = className.endsWith("Request") + val requiredFields = schema.path("required").mapNotNull { it.asText() }.toSet() + + val properties = schema.path("properties") + val recordComponents = mutableListOf() + val fieldDocs = StringBuilder() + properties.fields().forEach { (jsonName, propSchema) -> + val javaName = toCamelCase(jsonName) + val type = jsonSchemaToJavaType(propSchema) + val required = jsonName in requiredFields + + val paramBuilder = ParameterSpec.builder(type, javaName) + if (!required) { + paramBuilder.addAnnotation(Nullable::class.java) + } + // JsonProperty maps the snake_case wire name back to the + // camelCase record component. + paramBuilder.addAnnotation( + AnnotationSpec.builder( + ClassName.get("com.fasterxml.jackson.annotation", "JsonProperty") + ).addMember("value", "\$S", jsonName).build() + ) + recordComponents.add(paramBuilder.build()) + + val descr = propSchema.path("description").asText("") + if (descr.isNotBlank()) { + fieldDocs.append("@param $javaName ${escape(descr)}\n") + } + } + + val recordCtor = MethodSpec.constructorBuilder() + .addModifiers(Modifier.PUBLIC) + .addParameters(recordComponents) + .build() + + val typeBuilder = TypeSpec.recordBuilder(className) + .addModifiers(Modifier.PUBLIC) + .addJavadoc(schema.path("description").asText("(no description)") + "\n\n") + .addJavadoc(fieldDocs.toString()) + .addAnnotation(generatedAnnotation(schemaFile)) + .recordConstructor(recordCtor) + + if (isRequest) { + typeBuilder.addType(buildBuilder(className, recordComponents)) + typeBuilder.addMethod( + MethodSpec.methodBuilder("builder") + .addModifiers(Modifier.PUBLIC, Modifier.STATIC) + .returns(ClassName.bestGuess("Builder")) + .addStatement("return new Builder()") + .build() + ) + } + + val packageName = "$basePackage.${jsonPackagePath(schemaFile)}" + val javaFile = JavaFile.builder(packageName, typeBuilder.build()) + .skipJavaLangImports(true) + .indent(" ") + .build() + + val targetDir = outputDir.toPath().resolve(packageName.replace('.', '/')) + targetDir.toFile().mkdirs() + val targetFile = targetDir.resolve("$className.java") + targetFile.toFile().writeText(javaFile.toString()) + return targetFile + } + + private fun buildBuilder( + recordClassName: String, + components: List + ): TypeSpec { + val builderType = ClassName.bestGuess("Builder") + val recordType = ClassName.bestGuess(recordClassName) + + val builder = TypeSpec.classBuilder("Builder") + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .addJavadoc("Fluent builder for {@link \$T}. Per RFC §Type generation: " + + "*Request types always have builders; *Response types are records " + + "and never do.\n", recordType) + + components.forEach { c -> + builder.addField( + FieldSpec.builder(c.type(), c.name(), Modifier.PRIVATE) + .addAnnotation(Nullable::class.java) + .build() + ) + } + components.forEach { c -> + builder.addMethod( + MethodSpec.methodBuilder(c.name()) + .addModifiers(Modifier.PUBLIC) + .returns(builderType) + .addParameter(c.type(), c.name()) + .addStatement("this.\$N = \$N", c.name(), c.name()) + .addStatement("return this") + .build() + ) + } + builder.addMethod( + MethodSpec.methodBuilder("build") + .addModifiers(Modifier.PUBLIC) + .returns(recordType) + .addStatement( + "return new \$T(${components.joinToString(", ") { it.name() }})", + recordType + ) + .build() + ) + return builder.build() + } + + private fun jsonSchemaToJavaType(prop: JsonNode): TypeName { + return when (prop.path("type").asText("")) { + "string" -> ClassName.get("java.lang", "String") + "integer" -> ClassName.get("java.lang", "Integer") + "boolean" -> ClassName.get("java.lang", "Boolean") + "number" -> ClassName.get("java.lang", "Double") + "array" -> ParameterizedTypeName.get( + ClassName.get("java.util", "List"), + jsonSchemaToJavaType(prop.path("items")) + ) + else -> ClassName.get("java.lang", "Object") + } + } + + private fun generatedAnnotation(source: File): AnnotationSpec { + return AnnotationSpec.builder( + ClassName.get("javax.annotation.processing", "Generated") + ) + .addMember("value", "\$S", "org.adcontextprotocol.adcp.codegen.SchemaCodegen") + .addMember("comments", "\$S", "from ${source.name}") + .build() + } + + private fun jsonPackagePath(schemaFile: File): String { + // schemas/core/pagination-request.json -> "core" + // schemas/media-buy/get-products-request.json -> "media_buy" + val parent = schemaFile.parentFile.name + return parent.replace('-', '_').lowercase() + } + + private fun toClassName(title: String): String = + title.split(" ", "-", "_").joinToString("") { it.replaceFirstChar(Char::uppercaseChar) } + + private fun toCamelCase(jsonName: String): String { + val parts = jsonName.split("_") + return parts.first() + parts.drop(1).joinToString("") { + it.replaceFirstChar(Char::uppercaseChar) + } + } + + private fun escape(text: String): String = text.replace("\$", "\$\$") +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 04d8399..25159d8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -23,6 +23,9 @@ mcp-sdk = "1.1.2" # prototype question: can it run without Jetty/Tomcat?). jakarta-servlet = "6.1.0" +# Codegen (build-time only). +javapoet = "0.7.0" + # Test. junit = "5.11.4" @@ -72,5 +75,8 @@ kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-c # CLI runtime logging slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" } +# Codegen (build-time only — never on the SDK's runtime classpath) +javapoet = { module = "com.palantir.javapoet:javapoet", version.ref = "javapoet" } + [plugins] kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } From 8628d34c24657d24833f1b54d89e9bde37000aeb Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Wed, 13 May 2026 16:55:08 -0400 Subject: [PATCH 11/12] build(server): pin MCP SDK to mcp-core + mcp-json-jackson2 (closes D9 R1, R2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes both prototype questions D9 left open for harness Week 1. R1 — HTTP server transport without Jetty/Tomcat: **Yes.** mcp-core 1.1.2 ships three framework-neutral server transport providers in the JAR itself: - HttpServletStreamableServerTransportProvider (current MCP spec) - HttpServletSseServerTransportProvider (older SSE form) - HttpServletStatelessServerTransport (stateless variant) - StdioServerTransportProvider (no HTTP at all) They compile against jakarta.servlet-api 6.x but don't depend on Jetty, Tomcat, Undertow, or anything Spring. Adopters bring their own Servlet container at runtime. mcp-spring-webmvc and mcp-spring-webflux exist as optional artifacts for Spring-specific integration — we don't need them. The 0.x line had a separate `server-servlet` artifact; 1.x folded it into mcp-core itself. R2 — Jackson 2 vs Jackson 3 module parity: **Both at 1.1.2, identical cadence, neither lags.** Same surface; choice is which Jackson tree the consumer's other deps use. We pick mcp-json-jackson2 because RFC §JSON pins Jackson 2.15+. Pulling mcp-json-jackson3 would create a dual- Jackson dep graph for adopters — exactly the class of problem RFC's Jackson pin prevents. Build wiring: - gradle/libs.versions.toml — replaces `mcp` (the bundle, which pulls jackson3) with explicit mcp-core + mcp-json-jackson2 entries. - adcp-server/build.gradle.kts — same swap; comments cite findings doc. specs/mcp-prototype-findings.md is the full write-up — closes the open questions per D16 (longer-form decisions in specs/). Smoke test in adcp-server: 12 tests covering presence of the four server transports, three client transports, three spec interfaces, the stdio-no-servlet load path, and the Jackson 2 (not 3) classpath assertion. Uses getResource(name + ".class") instead of Class.forName because the servlet-extending classes need jakarta.servlet-api at runtime — a compileOnly dep adopters provide. The contract we test is "the SDK ships the class," not "we provide the adopter's runtime." Full build green: 51 tests across :adcp (4 codegen + 35 SSRF + 12 MCP imports). On JDK 21 + Gradle 9.5.1. Co-Authored-By: Claude Opus 4.7 (1M context) --- adcp-server/build.gradle.kts | 12 ++- .../adcp/server/McpDependencyImportsTest.java | 70 +++++++++++++++ gradle/libs.versions.toml | 9 +- specs/mcp-prototype-findings.md | 90 +++++++++++++++++++ 4 files changed, 176 insertions(+), 5 deletions(-) create mode 100644 adcp-server/src/test/java/org/adcontextprotocol/adcp/server/McpDependencyImportsTest.java create mode 100644 specs/mcp-prototype-findings.md diff --git a/adcp-server/build.gradle.kts b/adcp-server/build.gradle.kts index 3215e9d..6e648b4 100644 --- a/adcp-server/build.gradle.kts +++ b/adcp-server/build.gradle.kts @@ -11,8 +11,14 @@ description = "AdCP Java SDK — server-side primitives, signing, async tasks, w dependencies { api(project(":adcp")) - api(libs.mcp) - // jakarta.servlet-api is pulled in by mcp-core's HTTP server transport. - // D9 R1 prototype question: can this run without Jetty/Tomcat? + // mcp-core ships HttpServletSseServerTransportProvider and + // HttpServletStreamableServerTransportProvider — framework-neutral + // (no Spring dependency). mcp-json-jackson2 pins us to the Jackson 2 + // tree per RFC §JSON. See specs/mcp-prototype-findings.md. + api(libs.mcp.core) + api(libs.mcp.json.jackson2) + // The servlet transport classes use jakarta.servlet.* at compile time; + // the adopter brings their own Servlet container at runtime (Jetty, + // Tomcat, Undertow, embedded Spring Boot, etc.). compileOnly(libs.jakarta.servlet.api) } diff --git a/adcp-server/src/test/java/org/adcontextprotocol/adcp/server/McpDependencyImportsTest.java b/adcp-server/src/test/java/org/adcontextprotocol/adcp/server/McpDependencyImportsTest.java new file mode 100644 index 0000000..b122040 --- /dev/null +++ b/adcp-server/src/test/java/org/adcontextprotocol/adcp/server/McpDependencyImportsTest.java @@ -0,0 +1,70 @@ +package org.adcontextprotocol.adcp.server; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * Smoke test for the MCP SDK 1.1.2 dependency wiring (D9 R1 + R2). + * + *

This is the "deps resolve" gate — proves that mcp-core + mcp-json-jackson2 + * are on the classpath and the framework-neutral servlet transport providers + * exist where {@code specs/mcp-prototype-findings.md} says they do. + * + *

We assert presence via {@code getResource(name + ".class")} rather than + * {@code Class.forName(...)} because the servlet transports extend + * {@code HttpServlet} from {@code jakarta.servlet-api} — a {@code compileOnly} + * dep that adopters provide at runtime. Loading those classes via reflection + * would NoClassDefFoundError without the servlet API; checking the + * {@code .class} resource exists proves the SDK ships the class without + * needing the transitive dep that adopters bring. + * + *

Full integration tests for the transport land on the {@code transport} + * track. + */ +class McpDependencyImportsTest { + + @ParameterizedTest(name = "ships {0}") + @ValueSource(strings = { + "io.modelcontextprotocol.server.transport.HttpServletStreamableServerTransportProvider", + "io.modelcontextprotocol.server.transport.HttpServletSseServerTransportProvider", + "io.modelcontextprotocol.server.transport.HttpServletStatelessServerTransport", + "io.modelcontextprotocol.server.transport.StdioServerTransportProvider", + "io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport", + "io.modelcontextprotocol.client.transport.HttpClientSseClientTransport", + "io.modelcontextprotocol.client.transport.StdioClientTransport", + "io.modelcontextprotocol.spec.McpServerTransportProvider", + "io.modelcontextprotocol.spec.McpStreamableServerTransportProvider", + "io.modelcontextprotocol.spec.McpClientTransport" + }) + void mcp_core_classes_present(String fqcn) { + String resource = fqcn.replace('.', '/') + ".class"; + assertNotNull( + getClass().getClassLoader().getResource(resource), + fqcn + " not on classpath — has the MCP SDK pin moved?"); + } + + @Test + void stdio_transport_loads_without_servlet() throws Exception { + // StdioServerTransportProvider has no servlet dep, so Class.forName + // works. Proves the MCP SDK is at least partially callable from a + // plain JVM without bringing in any HTTP infrastructure. + Class cls = Class.forName( + "io.modelcontextprotocol.server.transport.StdioServerTransportProvider"); + assertNotNull(cls); + } + + @Test + void jackson2_module_is_pinned_not_jackson3() { + // mcp-json-jackson2 brings com.fasterxml.jackson.databind onto the + // classpath. The Jackson 3 module would sit under + // tools.jackson.databind — we deliberately don't load it; absence + // of that runtime dep is the contract. + String jackson2 = "com/fasterxml/jackson/databind/ObjectMapper.class"; + assertNotNull( + getClass().getClassLoader().getResource(jackson2), + "Jackson 2 not on classpath — has mcp-json-jackson2 been replaced by jackson3?"); + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 25159d8..5b72823 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -49,8 +49,13 @@ jspecify = { module = "org.jspecify:jspecify", version.ref = "jspecify" } # Validation json-schema-validator = { module = "com.networknt:json-schema-validator", version.ref = "json-schema-validator" } -# MCP -mcp = { module = "io.modelcontextprotocol.sdk:mcp", version.ref = "mcp-sdk" } +# MCP — depend on mcp-core (the framework-neutral substrate) + mcp-json-jackson2 +# (RFC §JSON pins Jackson 2.15+ floor). The `mcp` bundle artifact pulls +# mcp-json-jackson3 by default; we deliberately swap to jackson2 to align +# with the rest of the SDK's Jackson 2 dependency tree. +# Findings: specs/mcp-prototype-findings.md +mcp-core = { module = "io.modelcontextprotocol.sdk:mcp-core", version.ref = "mcp-sdk" } +mcp-json-jackson2 = { module = "io.modelcontextprotocol.sdk:mcp-json-jackson2", version.ref = "mcp-sdk" } # Servlet (for mcp-core HTTP transport) jakarta-servlet-api = { module = "jakarta.servlet:jakarta.servlet-api", version.ref = "jakarta-servlet" } diff --git a/specs/mcp-prototype-findings.md b/specs/mcp-prototype-findings.md new file mode 100644 index 0000000..9048b6a --- /dev/null +++ b/specs/mcp-prototype-findings.md @@ -0,0 +1,90 @@ +# MCP SDK 1.1.2 — harness prototype findings + +**Status:** Closes D9 R1 + R2 +**Tracks:** [`transport`](../ROADMAP.md#track-3--l0-transport-mcp--a2a) +**Decisions referenced:** D9 + +## What we needed to answer + +D9 picked `io.modelcontextprotocol.sdk:mcp:1.1.2` and flagged two open questions for harness Week 1: + +- **R1**: Can `mcp-core`'s servlet-based streamable-HTTP server transport run without Jetty/Tomcat? +- **R2**: Is `mcp-json-jackson2` feature-equivalent to the Jackson 3 module? + +Both are answered. The transport story is cleaner than the agent's initial research suggested. + +## R1 — HTTP server transport without Jetty/Tomcat + +**Yes.** `mcp-core` 1.1.2 ships three framework-neutral server transport providers directly in the JAR: + +| Class | Wire shape | +|---|---| +| `io.modelcontextprotocol.server.transport.HttpServletStreamableServerTransportProvider` | The current MCP streamable-HTTP spec | +| `io.modelcontextprotocol.server.transport.HttpServletSseServerTransportProvider` | SSE (deprecated by the spec but still useful for older clients) | +| `io.modelcontextprotocol.server.transport.HttpServletStatelessServerTransport` | Stateless variant for short-lived per-request connections | +| `io.modelcontextprotocol.server.transport.StdioServerTransportProvider` | stdio transport (no HTTP at all) | + +These compile against `jakarta.servlet.servlet-api` 6.x. They **don't** depend on Jetty, Tomcat, Undertow, or anything Spring. The adopter brings their own Servlet container at runtime — embedded Jetty, Spring Boot's embedded Tomcat, Undertow inside Quarkus, etc. + +The `mcp-spring-webmvc` and `mcp-spring-webflux` artifacts exist for Spring-specific integration but are **optional**. We don't need them. + +**Practical consequence:** `adcp-server` declares `jakarta.servlet-api` as `compileOnly` (already the case). Adopters who use the embedded Spring Boot starter get a servlet container for free; adopters who run plain JVM bring their own. No transitive heaviness in the core dep tree. + +### Caveat — `server-servlet` is a 0.x line + +There's a separate artifact `io.modelcontextprotocol.sdk:server-servlet` that goes up to 0.18.2 and never made the jump to 1.x. **Don't use it.** The 1.x line folded everything into `mcp-core` itself. Adopters who pinned the 0.x line and depended on `server-servlet` separately will need to migrate. + +## R2 — Jackson 2 vs. Jackson 3 module parity + +**Both modules are at 1.1.2 with identical version cadence.** Neither lags. + +- `io.modelcontextprotocol.sdk:mcp-json-jackson3` — the **default** brought in by the `mcp` bundle artifact. Targets the Jackson 3 line (which is still pre-release as of this writing). +- `io.modelcontextprotocol.sdk:mcp-json-jackson2` — explicit opt-in. Targets the Jackson 2 line. + +Both are thin bindings on top of the same `mcp-json` interface; they expose the same surface. Choosing one is a matter of which Jackson tree the consumer's other dependencies use. + +**We pick `mcp-json-jackson2`.** RFC §JSON pins `jackson-databind >= 2.15` (the records-support floor); the rest of the SDK is on the Jackson 2 tree. Pulling `mcp-json-jackson3` would create a dual-Jackson dep graph for adopters, which is exactly the class of problem the RFC's Jackson pin exists to prevent. + +### Build wiring + +```kotlin +// adcp-server/build.gradle.kts +dependencies { + api(libs.mcp.core) // ← framework-neutral substrate + api(libs.mcp.json.jackson2) // ← explicit Jackson 2 binding + compileOnly(libs.jakarta.servlet.api) +} +``` + +We **don't** use `io.modelcontextprotocol.sdk:mcp` (the bundle) because it pulls `mcp-json-jackson3` transitively. + +## Surface of `mcp-core` 1.1.2 + +For reference, the relevant top-level packages: + +| Package | Role | +|---|---| +| `io.modelcontextprotocol.spec` | Protocol-spec types: `McpTransport`, `McpServerTransportProvider`, `McpStreamableServerTransport`, message envelopes | +| `io.modelcontextprotocol.server` | Server-side: handler registration, capability negotiation, session lifecycle | +| `io.modelcontextprotocol.server.transport` | Concrete server transports (the four listed above) | +| `io.modelcontextprotocol.client` | Client-side: protocol client, session, capability handshake | +| `io.modelcontextprotocol.client.transport` | Concrete client transports: `HttpClientStreamableHttpTransport`, `HttpClientSseClientTransport`, `StdioClientTransport` | +| `io.modelcontextprotocol.auth` | Auth helpers (OAuth metadata, bearer) | +| `io.modelcontextprotocol.session` | Session management | + +301 classes total. Everything we need for the AdCP server-side surface lives here. + +## 2.0.0 migration (deliberate PR ~6 months out) + +The 2.0.0-M2 milestone is on Maven Central. Known breaks: + +- Sealed interfaces removed from message types +- `JsonSchema` typed parameter replaced with `Map` +- Tool input validation default flipped +- Server transport builder methods removed (constructors-only) + +We track on `1.1.2` for v0.1–v0.3. Migration to 2.x is a deliberate PR when 2.0 GAs (the M-line is still in flight). See D9's note in ROADMAP. + +## Smoke test in the repo + +`adcp-server/src/test/.../McpDependencyImportsTest.java` imports the four servlet transport providers and asserts each class loads. This is the harness-level "the deps resolve" gate — full integration tests for the transport land on the `transport` track. From 2d46253047f66fd4bce5cf800b9195bc0bd88f18 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Wed, 13 May 2026 16:55:48 -0400 Subject: [PATCH 12/12] docs(roadmap): mark harness progress, D9 R1/R2 closed, harness in flight MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit D9's two prototype questions are answered in specs/mcp-prototype-findings.md and the row in §Confirmed decisions now points at the findings instead of leaving them open. Status table gains a "Confirmed decisions D1-D21 locked" row and the pre-contributor harness item moves to 🟡 In progress with a one-liner of what's landed vs the foundation-admin items still outstanding (IPR Bot install, DNS TXT for Sonatype, @MichielDean collaborator). Co-Authored-By: Claude Opus 4.7 (1M context) --- ROADMAP.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 5aad254..e61256d 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -18,7 +18,7 @@ Decisions made post-RFC that supersede or refine the merged text. The table belo | D6 | Maven Central publish cadence | **Hold first publish until v0.3 alpha.** v0.1 and v0.2 ship as local Gradle artifacts / SNAPSHOT only. Sonatype OSSRH namespace claim + GPG key setup still happens harness Week 1 (slow path; 1–5 business-day ticket) — we just don't push artifacts until v0.3. | RFC §Build, distribution, governance (RFC said "Maven Central alpha from v0.1") | | D7 | `javax`/`jakarta` floor | **`jakarta` only**, Spring Boot 3.x floor. Single `adcp-spring-boot-starter` artifact, no compat starter, no community 2.7 port. Spring Boot 2.7 OSS support ended Nov 2025; anyone still on it has a vendor relationship. | Resolves RFC Open Question 6 in favor of option (a) | | D8 | Mock-server CI deployment | **Sidecar via `npx adcp mock-server`.** GitHub Actions Node step installs a pinned `@adcp/sdk` version, backgrounds one mock-server per specialism on a port range, Java tests hit `localhost`. The pinned `@adcp/sdk` version is the conformance oracle — bumping it is a deliberate PR. Promote to a published Docker image if multi-specialism orchestration becomes unwieldy. | Specifies D5's deployment | -| D9 | MCP Java SDK | **`io.modelcontextprotocol.sdk:mcp` pinned `1.1.2`** at the core. Used by `adcp` (caller) and `adcp-server` (agent). The Spring AI MCP SDK was donated to the `modelcontextprotocol` org in Feb 2025 and rebranded as the official Java SDK; current `spring-ai-mcp-*` artifacts are now thin Spring Boot wrappers on top of `io.modelcontextprotocol.sdk` — no parallel implementation. **License: MIT** (compatible, flagged for foundation position). Two harness Week 1 prototype questions left open: (a) whether `mcp-core`'s servlet-based streamable-HTTP server transport is usable without pulling Jetty/Tomcat; (b) Jackson 2 vs. 3 module split — confirm `mcp-json-jackson2` is feature-equivalent. | Resolves RFC Open Question 2 | +| D9 | MCP Java SDK | **`io.modelcontextprotocol.sdk:mcp-core:1.1.2` + `mcp-json-jackson2:1.1.2`** at the core (not the `mcp` bundle artifact, which pulls jackson3). Used by `adcp` (caller) and `adcp-server` (agent). The Spring AI MCP SDK was donated to the `modelcontextprotocol` org in Feb 2025 and rebranded as the official Java SDK; current `spring-ai-mcp-*` artifacts are now thin Spring Boot wrappers on top of it — no parallel implementation. **License: MIT** (compatible, flagged for foundation position). Both prototype questions closed in [`specs/mcp-prototype-findings.md`](specs/mcp-prototype-findings.md): (a) `HttpServletStreamableServerTransportProvider` in `mcp-core` is framework-neutral — no Jetty/Tomcat dep at compile time, adopter brings their own servlet container at runtime; (b) `mcp-json-jackson2` and `mcp-json-jackson3` are at identical 1.1.2 cadence with the same surface — we pin to jackson2 to match the rest of the SDK's Jackson tree. | Resolves RFC Open Question 2 | | D10 | A2A pre-1.0 type strategy | **Keep A2A types in-tree until `a2aproject/a2a-java` cuts a stable ≥ 1.0.0 release**, then migrate to the upstream client in one shot and deprecate the in-tree fallback in the next minor. As of the latest check, `a2a-java` is at `1.0.0.Beta1` (Apr 2026) — package layout still churning, so we don't hard-depend on it yet. | RFC default for Open Question 3 | | D11 | `TransitionGuard` narrowing protection | **Guards declare which spec edges they touch.** Conformance harness fails if a sandbox account's guards narrow any edge the storyboards exercise. Guards run after the spec edge check and can never relax a spec edge. | Resolves RFC Open Question 7 | | D12 | Spring Security integration depth | **Recipes-only at v1.0.** No separate `adcp-spring-boot-starter-security` artifact. Auth models vary too much to pre-bake; recipes age better than autoconfig. Revisit if v0.3 design-partner feedback demands it. | RFC default for Open Question 5 | @@ -526,7 +526,8 @@ Additional decisions added post-RFC that remain open: | RFC merged on adcontextprotocol/adcp | ✅ (PR #4279, 2026-05-13) | | RFC imported to this repo | ✅ ([docs/rfc/java-sdk-rfc.md](docs/rfc/java-sdk-rfc.md)) | | Implementation plan drafted | ✅ (this doc) | +| Confirmed decisions D1–D21 locked | ✅ | | Funding / staffing confirmed | ⏳ Decision pending | | Tracks claimed | 0 / 14 | -| Pre-contributor harness | Not Started | +| Pre-contributor harness | 🟡 In progress — Gradle skeleton, codegen MVP, SSRF skeleton, schema fetcher, mock-server CI gate, IPR workflow, commitlint, changesets, MCP prototype findings all landed. Foundation admin actions outstanding: IPR Bot install, DNS TXT for Sonatype, @MichielDean collaborator. | | v0.1 alpha | Not Started |