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/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/.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/.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/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c1f81e5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,38 @@ +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: 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/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/.github/workflows/storyboard.yml b/.github/workflows/storyboard.yml new file mode 100644 index 0000000..a91c83e --- /dev/null +++ b/.github/workflows/storyboard.yml @@ -0,0 +1,91 @@ +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: Install cosign + uses: sigstore/cosign-installer@v3 + + - 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/.gitignore b/.gitignore index 524f096..03c9122 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,29 @@ +# Gradle +.gradle/ +.kotlin/ +build/ +local.properties + +# Node — only for commitlint + Changesets tooling (per D18); the SDK itself is Java +node_modules/ +npm-debug.log* + +# IDE +.idea/ +*.iml +.vscode/ +.classpath +.project +.settings/ + +# Coding-agent side channels (Conductor workspace state, etc.) +.claude/ +.context/ +.local/ + +# OS +.DS_Store + # Compiled class file *.class @@ -10,8 +36,7 @@ # Mobile Tools for Java (J2ME) .mtj.tmp/ -# Package Files # -*.jar +# Package Files *.war *.nar *.ear @@ -19,6 +44,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_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/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). diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 0000000..e61256d --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,533 @@ +# 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. 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 | +|---|---|---|---| +| 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. 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-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 | +| 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) + +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) | +| 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 + 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 + +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. 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. **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 | + +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). +- 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. + +**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 + 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. + +--- + +### 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:** 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. +- 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. +- `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). + +**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. 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.~~ **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.~~ **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) + +- 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) | +| Confirmed decisions D1–D21 locked | ✅ | +| Funding / staffing confirmed | ⏳ Decision pending | +| Tracks claimed | 0 / 14 | +| 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 | diff --git a/adcp-cli/build.gradle.kts b/adcp-cli/build.gradle.kts new file mode 100644 index 0000000..d462ad7 --- /dev/null +++ b/adcp-cli/build.gradle.kts @@ -0,0 +1,26 @@ +// 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) + testImplementation(libs.junit.jupiter.params) + 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..6e648b4 --- /dev/null +++ b/adcp-server/build.gradle.kts @@ -0,0 +1,24 @@ +// 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")) + // 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/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-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/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-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/adcp/build.gradle.kts b/adcp/build.gradle.kts new file mode 100644 index 0000000..8a1ae54 --- /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.schema-codegen-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/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/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/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/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/build.gradle.kts b/build-logic/build.gradle.kts new file mode 100644 index 0000000..e8fb51c --- /dev/null +++ b/build-logic/build.gradle.kts @@ -0,0 +1,21 @@ +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}" + }) + + // 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/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..437bf99 --- /dev/null +++ b/build-logic/src/main/kotlin/adcp.java-library-conventions.gradle.kts @@ -0,0 +1,14 @@ +// 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) + "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 new file mode 100644 index 0000000..a0de2ba --- /dev/null +++ b/build-logic/src/main/kotlin/adcp.kotlin-library-conventions.gradle.kts @@ -0,0 +1,18 @@ +// 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) + "testImplementation"(libs.junit.jupiter.params) + "testRuntimeOnly"(libs.junit.jupiter.engine) + "testRuntimeOnly"(libs.junit.platform.launcher) +} 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 +} 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/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/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/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 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..5b72823 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,87 @@ +# 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" + +# Codegen (build-time only). +javapoet = "0.7.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 — 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" } + +# 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" } + +# 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" } + +# 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" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..b1b8ef5 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ 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/package-lock.json b/package-lock.json new file mode 100644 index 0000000..12899b3 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3807 @@ +{ + "name": "adcp-sdk-java-tools", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "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", + "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/@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", + "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/@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", + "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/@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", + "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/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", + "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/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", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "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", + "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/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", + "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/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", + "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/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", + "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/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", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "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", + "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/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", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "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", + "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/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", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "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", + "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, + "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/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", + "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/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", + "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/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", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "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", + "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/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", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "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", + "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/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", + "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "dev": true, + "license": "ISC", + "engines": { + "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", + "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-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", + "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/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", + "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/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", + "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/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", + "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.10" + }, + "funding": { + "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", + "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/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", + "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/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", + "peer": true + }, + "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", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "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==", + "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/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", + "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-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", + "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/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", + "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/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", + "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/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", + "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/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", + "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/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", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "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", + "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/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", + "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/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", + "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/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", + "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/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", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "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", + "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": { + "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", + "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/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", + "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/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", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "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", + "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" + } + }, + "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 new file mode 100644 index 0000000..ff327be --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "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": { + "@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" + } +} 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" +) 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. 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