diff --git a/.llm/runs/fix-onboarding-quickwin-1250--1250/context-pack.md b/.llm/runs/fix-onboarding-quickwin-1250--1250/context-pack.md new file mode 100644 index 0000000000..94da23c50b --- /dev/null +++ b/.llm/runs/fix-onboarding-quickwin-1250--1250/context-pack.md @@ -0,0 +1,76 @@ +# Context Pack: restore Zod-4 OpenAPI query coercion (#1250) + +## Run Metadata + +| Field | Value | +| --- | --- | +| Run ID | `fix-onboarding-quickwin-1250--1250` | +| Branch | `fix/onboarding-quickwin-1250` | +| Current phase | `gate` | +| Archetype | `4 — Public DSL / Builder` | +| Scope overlays | `service` | + +## Current State + +The Zod-4 coercion adapter and the docs-shaped HTTP regression are green. All locally required +targeted gates pass; the slice is ready to commit, push, comment, and hand to composed evaluation. + +## Completed + +- Read issue body, relevant skills, harness authorities, doctrine, package surface, tests, and + upstream oRPC Zod-4 export. +- Recorded the inherited unrelated `deno.lock` change. +- Captured a pre-fix HTTP 400 negative control. +- Made the unchanged request return HTTP 200 with numeric `cycleId: 1` and verified its generated + OpenAPI parameter is numeric. +- Passed scoped check/lint/fmt, 87 package tests, 5 MCP OpenAPI read-tool tests, code-quality, + doctrine, doc-lint, and publish dry-run gates. + +## In Progress + +- Commit and push the implementation/evidence slice, update PR/issue evidence, and transition the + PR to the milestone-composed evaluation surface. + +## Next Steps + +1. Commit and push source, test, and run evidence without staging `deno.lock`. +2. Post the implementation phase comment and update the structured acceptance evidence. +3. Mark PR #1256 ready for the draft→ready augment/OpenHands/orchestrator composition. + +## Key Decisions + +| Decision | Source | Notes | +| --- | --- | --- | +| Alias Zod-4 experimental export | upstream API inspection | Internal-only name adaptation. | +| Test actual query transport | issue #1250 | Prevents present-but-inert false green. | +| Preserve dirty lockfile | owner + AGENTS.md | Never stage it. | + +## Files Changed + +| Path | Status | Notes | +| --- | --- | --- | +| `.llm/runs/fix-onboarding-quickwin-1250--1250/**` | new | Harness activation and locked plan. | +| `packages/service/src/primitives/handlers.ts` | changed | Selects the Zod-4 smart coercion adapter. | +| `packages/service/tests/handlers_test.ts` | changed | HTTP + generated OpenAPI numeric-query regression. | + +## Gates + +| Gate family | Current status | Evidence | +| --- | --- | --- | +| Static | PASS | 42-file scoped wrappers; doc-lint; publish dry-run | +| Fitness | PASS | scoped quality 0/0; doctrine 0 FAIL; repository quality gate exit 0 | +| Runtime | PASS | RED 400 → GREEN 200 with numeric `cycleId: 1` | +| Consumer | PASS | service 87/87; MCP OpenAPI tools 5/5 | + +## Open Questions + +- None blocking. + +## Drift and Debt + +- Drift: authorized milestone evaluation composition; inherited lock modification. +- Debt: no new/deepened debt expected. + +## Commits + +- See the draft PR's commit list + per-slice PR comments. diff --git a/.llm/runs/fix-onboarding-quickwin-1250--1250/drift.md b/.llm/runs/fix-onboarding-quickwin-1250--1250/drift.md new file mode 100644 index 0000000000..c7df525d5a --- /dev/null +++ b/.llm/runs/fix-onboarding-quickwin-1250--1250/drift.md @@ -0,0 +1,33 @@ +# Drift Log: restore Zod-4 OpenAPI query coercion (#1250) + +## 2026-08-04 — Milestone composed PLAN-EVAL + +- **What:** No local formal PLAN-EVAL is spawned for this delegated milestone PR. +- **Source:** Owner instruction; `milestone-run.md`; orchestrator ruling D6. +- **Expected:** Generic run-loop uses a separate formal PLAN-EVAL before implementation. +- **Actual:** The plan is locked locally and evaluation composes draft→ready augment, OpenHands, + and the orchestrator pre-merge gate. +- **Severity:** minor / authorized +- **Action:** accept +- **Evidence:** `supervisor.md`, `plan.md` D6, `plan-eval.md`. + +## 2026-08-04 — Inherited lockfile modification + +- **What:** The supplied worktree began with a one-line `deno.lock` addition not made by this run. +- **Source:** Bootstrap `git status` and `git diff -- deno.lock`. +- **Expected:** Clean worktree other than run-owned files. +- **Actual:** `jsr:@netscript/queue@0.0.4` is added to an existing lock package dependency list. +- **Severity:** minor +- **Action:** preserve and exclude from every commit +- **Evidence:** raw worktree diff before source changes. + +## 2026-08-04 — Introspection tools describe but do not execute operations + +- **What:** Issue acceptance says the same request shape works through #1204 introspection tools. +- **Source:** Live issue #1250; MCP tool registry and application flows on current main. +- **Expected:** A tool capable of issuing the documented request could prove execution directly. +- **Actual:** `list_api_services`, `list_service_operations`, and `get_operation_schema` discover and + project OpenAPI; none calls a service operation. The HTTP handler is the executable boundary. +- **Severity:** minor / specification clarification +- **Action:** accept; prove HTTP request execution and retain existing introspection schema tests +- **Evidence:** `packages/mcp/src/application/flows/`, MCP tool catalog. diff --git a/.llm/runs/fix-onboarding-quickwin-1250--1250/plan-eval.md b/.llm/runs/fix-onboarding-quickwin-1250--1250/plan-eval.md new file mode 100644 index 0000000000..0ce8d16bfb --- /dev/null +++ b/.llm/runs/fix-onboarding-quickwin-1250--1250/plan-eval.md @@ -0,0 +1,17 @@ +# PLAN-EVAL — fix-onboarding-quickwin-1250--1250 + +| Gate row | Result | Evidence | +| --- | --- | --- | +| Research current | PASS | `research.md`; branch and `origin/main` share `5957260751`. | +| Decisions locked | PASS | `plan.md` D1–D6. | +| Open-decision sweep | PASS | No implementation-rework decision remains open. | +| Commit slices | PASS | Bootstrap, red proof, implementation/test, and gate evidence are ordered in `worklog.md`. | +| Risk register | PASS | No-match, hidden string, upstream name, publication, and lock risks covered. | +| Gate set selected | PASS | Archetype-4, service overlay, package, behavior, doctrine, and JSR gates named. | +| Deferred scope explicit | PASS | Contract, Scalar, MCP execution, dependency bump, and full E2E excluded. | +| jsr-audit | PASS | No public-surface delta; doc-lint and package dry-run retained. | +| Formal evaluator | composed per `milestone-run.md` (orchestrator waiver) | Explicit owner ruling D6; no local PLAN-EVAL launched. | + +The plan is locked. This artifact records the authorized milestone composition and does not claim a +self-issued formal evaluator verdict. + diff --git a/.llm/runs/fix-onboarding-quickwin-1250--1250/plan.md b/.llm/runs/fix-onboarding-quickwin-1250--1250/plan.md new file mode 100644 index 0000000000..003124ee1b --- /dev/null +++ b/.llm/runs/fix-onboarding-quickwin-1250--1250/plan.md @@ -0,0 +1,129 @@ +# Plan: restore Zod-4 OpenAPI query coercion (#1250) + +## Run Metadata + +| Field | Value | +| --- | --- | +| Run ID | `fix-onboarding-quickwin-1250--1250` | +| Branch | `fix/onboarding-quickwin-1250` | +| Phase | `plan` | +| Target | `packages/service` OpenAPI handler | +| Archetype | `4 — Public DSL / Builder` | +| Scope overlays | `service` | + +## Archetype + +The doctrine census classifies `@netscript/service` as Archetype 4. This fix changes a primitive +materialized by the service builder and does not redesign lifecycle, state, or the public builder. + +## Current Doctrine Verdict + +`@netscript/service`: **Refactor** — clarify `presets/` and vendored Scalar asset roles. Those +existing debt entries are unaffected by this narrow adapter correction. + +## Axioms in Play + +| Axiom | Why it matters | +| --- | --- | +| A2 | A declared numeric HTTP input must work without hidden schema-version mismatch. | +| A7 | Use the upstream oRPC Zod-4 adapter rather than local coercion. | +| A14 | The test must prove observable request behavior, not plugin construction. | + +## Goal + +Make the Scalar/docs-shaped `GET /issues/board?cycleId=1` request reach a Zod-4 handler with +`cycleId` coerced to the number `1`, and fail regression tests if coercion becomes inert again. + +## Scope + +- Select the Zod-4 smart coercion plugin subpath in `handlers.ts`. +- Add an HTTP-boundary regression test in `packages/service/tests/handlers_test.ts`. +- Record package, doctrine, and targeted validation evidence. + +## Non-Scope + +- No contract schema changes, Scalar client patch, public export change, dependency bump, or full + CLI E2E run. +- No MCP execution tool is invented; #1204's tools describe rather than call service operations. +- No unrelated `packages/service` doctrine debt remediation. + +## Hidden Scope + +- The Zod-4 export is named `experimental_ZodSmartCoercionPlugin`; the import must alias it to keep + the implementation readable without exposing a new NetScript API. +- A test that merely inspects the plugin array is insufficient because the reported failure is a + silently installed no-op. + +## Locked Decisions + +| ID | Decision | Rationale | +| --- | --- | --- | +| D1 | Alias the upstream Zod-4 plugin export to `ZodSmartCoercionPlugin`. | Correct adapter with a minimal implementation delta. | +| D2 | Exercise `createOpenAPIHandler.handle()` using a real `Request` with `cycleId=1`. | Proves the transport string becomes a Zod number and catches inert plugins. | +| D3 | Assert status, match, and returned numeric value. | Prevents false green from a non-matched route or an error envelope. | +| D4 | Keep all public exports and contracts unchanged. | The defect is adapter selection, not API design. | +| D5 | Preserve inherited `deno.lock` modification and exclude it from commits. | Owner lock-hygiene rule. | +| D6 | Do not launch a local PLAN-EVAL; use milestone composed evaluation. | Explicit owner/orchestrator ruling. | + +## Open-Decision Sweep + +| Decision | Status | Notes | +| --- | --- | --- | +| OpenAPI query coercion mechanism | resolved now | Upstream Zod-4 plugin, per issue diagnosis. | +| MCP request execution | safe to defer | The current introspection contract has no execution operation; no new tool is authorized. | + +## Risk Register + +| Risk | Mitigation | +| --- | --- | +| Test passes without route match | Assert `matched === true`, HTTP 200, and decoded body. | +| Handler observes a string but output hides it | Handler returns `typeof` and value; assert number and `1`. | +| Import name mismatch | Confirmed through `deno doc` and upstream declaration export list. | +| Package publication regression | Run scoped check/lint/fmt, doc-lint, package test, and publish dry-run. | +| Lock churn enters PR | Stage explicit paths and compare `deno.lock` against baseline. | + +## Anti-Patterns to Resolve or Avoid + +| AP | Status | Plan | +| --- | --- | --- | +| AP-14 | risk | Do not re-export the upstream plugin; keep it internal. | +| AP-15 | risk | Keep caller vocabulary and public surface unchanged. | +| AP-25 | risk | Add no load-time or filesystem side effects. | + +## Fitness Gates + +| Gate | Required | Expected evidence | +| --- | --- | --- | +| F-1–F-19 applicable Archetype-4 set | yes | `quality:gate`, scoped wrappers, manual diff review | +| F-5/F-7 | yes | unchanged exports plus `doc:lint` | +| F-6 | yes | package `publish:dry-run` | +| F-10 | yes | regression test fails for inert plugin and passes for Zod-4 plugin | +| Runtime/consumer | yes | real OpenAPI handler request plus package test task | + +## Arch-Debt Implications + +| Entry | Action | Notes | +| --- | --- | --- | +| `packages/service — doctrine verdict Refactor` | none | Existing folder-role debt is not deepened. | +| `packages/service — assets/scalar.min.js` | none | No asset change. | + +## Validation Plan + +| Order | Gate | Command or check | Expected result | +| --- | --- | --- | --- | +| 1 | red proof | Run new handler test before import fix | Fails with HTTP 400/string validation error. | +| 2 | regression | `deno test --allow-all packages/service/tests/handlers_test.ts` | Pass. | +| 3 | scoped type | `.llm/tools/run-deno-check.ts --root packages/service --ext ts,tsx` | Pass with `--unstable-kv` inherited by wrapper. | +| 4 | scoped lint/fmt | repo scoped wrappers for `packages/service` | Pass. | +| 5 | package | `deno task --cwd packages/service test` | Pass. | +| 6 | doctrine | `deno task quality:gate` | Pass. | +| 7 | JSR | `deno task doc:lint --root packages/service --pretty`; package publish dry-run | Pass. | + +## Dependencies + +- Existing `@orpc/zod` 1.14.6 import-map entry; no version change. + +## Drift Watch + +- A deeper coercion defect, a need to patch Scalar, or lockfile delta would exceed the planned fix. + diff --git a/.llm/runs/fix-onboarding-quickwin-1250--1250/research.md b/.llm/runs/fix-onboarding-quickwin-1250--1250/research.md new file mode 100644 index 0000000000..97694741f7 --- /dev/null +++ b/.llm/runs/fix-onboarding-quickwin-1250--1250/research.md @@ -0,0 +1,32 @@ +# Research — fix-onboarding-quickwin-1250--1250 + +## Re-baseline + +- Carried-in source: owner brief and GitHub issue #1250. +- Re-derived against `origin/main` @ `5957260751f23d675d32bd7fb7b7a9198be84096` on 2026-08-04. +- The branch is byte-identical to current `origin/main`; the only inherited worktree change is an + unrelated one-line `deno.lock` addition, which this run preserves and excludes. + +## Findings + +| # | Finding | How to verify | +| --- | --- | --- | +| 1 | `handlers.ts` installs the Zod-3 coercion plugin while scaffold contracts use Zod 4. | `packages/service/src/primitives/handlers.ts:25`; issue #1250 | +| 2 | The sibling OpenAPI generator already selects the Zod-4 adapter. | `packages/service/src/primitives/openapi.ts:21` | +| 3 | oRPC 1.14.6 exposes the Zod-4 plugin as `experimental_ZodSmartCoercionPlugin`. | `deno doc npm:@orpc/zod@1.14.6/zod4`; cached declaration export list | +| 4 | Existing service handler tests never send a query-string number through the OpenAPI handler. | `packages/service/tests/handlers_test.ts` | +| 5 | The #1204 introspection surface describes OpenAPI operations but does not execute them; the HTTP handler remains the behavior-owning boundary. | MCP tool catalog and `packages/mcp/src/application/flows/` | + +## JSR audit surface scan + +- Surface scanned: `packages/service/deno.json`, `mod.ts`, and `deno doc --filter + createOpenAPIHandler packages/service/mod.ts`. +- Planned delta is an internal dependency subpath plus a test; no export, metadata, permission, or + documentation contract changes. +- Final package doc-lint and publish dry-run remain gates because the implementation file is in the + publish include set. + +## Open questions + +- None that would force rework. The Zod-4 export alias and HTTP regression shape are resolved. + diff --git a/.llm/runs/fix-onboarding-quickwin-1250--1250/supervisor.md b/.llm/runs/fix-onboarding-quickwin-1250--1250/supervisor.md new file mode 100644 index 0000000000..24d3a04a5f --- /dev/null +++ b/.llm/runs/fix-onboarding-quickwin-1250--1250/supervisor.md @@ -0,0 +1,32 @@ +# Supervisor Identity — fix-onboarding-quickwin-1250--1250 + +Written at run start per `workflow/lane-policy.md` § Supervisor identity. + +| Field | Value | +| --- | --- | +| Model | OpenAI · GPT-5.6 Sol · medium (owner-specified) | +| Session | Current Codex workspace session; external thread id is not exposed to the checkout | +| Host | Linux / WSL worktree host | +| Checkout | `/home/codex/repos/ns005-quickwins` | +| Worktree | `/home/codex/repos/ns005-quickwins` | +| Branch | `fix/onboarding-quickwin-1250` | +| Baseline | `5957260751f23d675d32bd7fb7b7a9198be84096` (`origin/main`, 2026-08-04) | +| Run ID | `fix-onboarding-quickwin-1250--1250` | + +## Routes in force + +| Task lane | Provider / model / effort | Role in this run | +| --- | --- | --- | +| owner-specified implementation lane | OpenAI · GPT-5.6 Sol · medium | Contract-first implementation and targeted gate evidence | +| milestone composed evaluation | Draft→ready augment + OpenHands + orchestrator pre-merge gate | Independent evaluation surface | + +## Recorded lane/eval overrides + +- The owner fixed the implementation identity at OpenAI · GPT-5.6 Sol · medium; it must not be + escalated or reduced. +- Milestone ruling D6 waives a local formal PLAN-EVAL. `plan-eval.md` records the composed row; + draft→ready augment, label-triggered OpenHands, and the orchestrator pre-merge gate retain + independent evaluation. +- This is one PR-sized run in a sequential three-PR lane, not a multi-group supervisor integration + run. + diff --git a/.llm/runs/fix-onboarding-quickwin-1250--1250/worklog.md b/.llm/runs/fix-onboarding-quickwin-1250--1250/worklog.md new file mode 100644 index 0000000000..14c83c3588 --- /dev/null +++ b/.llm/runs/fix-onboarding-quickwin-1250--1250/worklog.md @@ -0,0 +1,112 @@ +# Worklog: restore Zod-4 OpenAPI query coercion (#1250) + +## Run Metadata + +| Field | Value | +| --- | --- | +| Run ID | `fix-onboarding-quickwin-1250--1250` | +| Branch | `fix/onboarding-quickwin-1250` | +| Archetype | `4 — Public DSL / Builder` | +| Scope overlays | `service` | + +## Design + +### Public Surface + +- `createOpenAPIHandler` remains unchanged; its documented coercion guarantee becomes true for + scaffolded Zod-4 contracts. + +### Domain Vocabulary + +- HTTP query input — transport strings decoded by oRPC. +- smart coercion plugin — upstream schema-aware conversion before Zod validation. + +### Ports + +- `FetchHandler.handle(Request, options)` — existing Web Request boundary used for the regression. + +### Constants + +- None added. + +### Commit Slices + +| # | Slice | Gate | Files | +| --- | --- | --- | --- | +| 0 | Activate run and lock plan | artifact review | `.llm/runs/fix-onboarding-quickwin-1250--1250/**` | +| 1 | Prove inert plugin failure, then select Zod-4 adapter | focused red/green test | `packages/service/tests/handlers_test.ts`, `packages/service/src/primitives/handlers.ts` | +| 2 | Record package and doctrine gates | targeted gates | run artifacts only | + +### Deferred Scope + +- An MCP operation-execution tool — #1204 only exposes discovery/schema introspection. +- Full scaffold runtime E2E — explicitly owned by the milestone orchestrator gate. + +### Contributor Path + +Future handler adapter changes must keep the HTTP-boundary numeric query regression green; plugin +construction alone is not sufficient evidence. + +## Progress Log + +| Time | Slice | Step | Notes | +| --- | --- | --- | --- | +| 2026-08-04 | 0 | bootstrap | Issue body read first; baseline, doctrine, upstream API, and inherited lock change recorded. | +| 2026-08-04 | 0 | draft | Bootstrap commit `cf773ec38` pushed explicitly; draft PR #1256 opened with milestone and taxonomy. | +| 2026-08-04 | 1 | RED | Focused test matched the route but received HTTP 400 with the Zod-3 plugin. | +| 2026-08-04 | 1 | GREEN | Zod-4 adapter alias made the unchanged request return HTTP 200 and numeric `cycleId: 1`. | +| 2026-08-04 | 1 | introspection | Generated OpenAPI declares `cycleId` as a numeric query parameter; #1204 read-tool suite is 5/5 green. | +| 2026-08-04 | 2 | gates | Scoped/package/doctrine/JSR gates completed; no new lock delta or debt. | + +## Decisions + +| Decision | Reason | Source | +| --- | --- | --- | +| Zod-4 plugin alias | Upstream publishes the compatible class under an experimental name. | `deno doc npm:@orpc/zod@1.14.6/zod4` | +| HTTP behavior test | Catches the silent no-op failure class. | issue #1250; plan D2–D3 | +| No local PLAN-EVAL | Milestone evaluation is composed. | owner ruling D6 | + +## Drift + +| Drift | Severity | Logged in drift.md | +| --- | --- | --- | +| Generic PLAN-EVAL replaced by milestone composition | minor / authorized | yes | +| Inherited `deno.lock` change | minor | yes | + +## Gate Results + +### Static Gates + +| Gate | Command or check | Result | Notes | +| --- | --- | --- | --- | +| scoped check | `run-deno-check.ts --root packages/service --ext ts,tsx` | PASS | 42 files, 0 findings; uses `--unstable-kv`. | +| scoped lint | `run-deno-lint.ts --root packages/service --ext ts,tsx` | PASS | 42 files, 0 findings. | +| scoped fmt | `run-deno-fmt.ts --root packages/service --ext ts,tsx` | PASS | 42 files, 0 findings. | +| doc-lint | `deno task doc:lint --root packages/service --pretty` | PASS | 3 entrypoints, 0 diagnostics. | +| publish dry-run | `deno task --cwd packages/service publish:dry-run` | PASS | No slow types; intended publish set only. | + +### Fitness Gates + +| Gate | Result | Evidence | Notes | +| --- | --- | --- | --- | +| code quality | PASS | scanner `--root packages/service --max-allow 0` | 0 findings, 0 allowances. | +| doctrine | PASS with baseline warnings | `check-doctrine.ts --root packages/service --text` | 0 FAIL; existing Scalar-generated inheritance and builder-size warnings only. | +| repository `quality:gate` | PASS | task exit 0 | Existing repository dependency/doctrine warnings only. | + +### Runtime Gates + +| Gate | Result | Evidence | Notes | +| --- | --- | --- | --- | +| pre-fix negative control | PASS | focused test before source edit | Route matched and returned 400 instead of expected 200. | +| docs-shaped numeric GET | PASS | `handlers_test.ts` | Generated schema says numeric query; HTTP string `1` reaches handler as number `1`. | + +### Consumer Gates + +| Consumer | Result | Evidence | Notes | +| --- | --- | --- | --- | +| `packages/service` suite | PASS | `deno task --cwd packages/service test` | 87 passed, 0 failed. | +| MCP OpenAPI read tools | PASS | `openapi-read-tools_test.ts` | 5 passed, 0 failed; schema projection and curl template remain green. | + +## Handoff Notes + +- Review the test's observable numeric value first; it is the decisive acceptance property. diff --git a/packages/service/src/primitives/handlers.ts b/packages/service/src/primitives/handlers.ts index 1cdd5efcef..13b362eb92 100644 --- a/packages/service/src/primitives/handlers.ts +++ b/packages/service/src/primitives/handlers.ts @@ -22,7 +22,7 @@ import { RPCHandler } from '@orpc/server/fetch'; import { OpenAPIHandler } from '@orpc/openapi/fetch'; import { CORSPlugin } from '@orpc/server/plugins'; -import { ZodSmartCoercionPlugin } from '@orpc/zod'; +import { experimental_ZodSmartCoercionPlugin as ZodSmartCoercionPlugin } from '@orpc/zod/zod4'; import { createServiceLogger } from '@netscript/logger'; import { LoggingPlugin } from '@netscript/logger/orpc'; import { ErrorHandlingPlugin, TracingPlugin } from '@netscript/telemetry/orpc'; diff --git a/packages/service/tests/handlers_test.ts b/packages/service/tests/handlers_test.ts index d8b8e0f6c7..8c86783f0b 100644 --- a/packages/service/tests/handlers_test.ts +++ b/packages/service/tests/handlers_test.ts @@ -1,6 +1,58 @@ import { assertEquals } from '@std/assert'; +import { os } from '@orpc/server'; import { Hono } from 'hono'; -import { createErrorHandler, createNotFoundHandler } from '../mod.ts'; +import { z } from 'jsr:@zod/zod@4.4.3'; +import { + createErrorHandler, + createNotFoundHandler, + createOpenAPIHandler, + createOpenAPISpec, +} from '../mod.ts'; + +interface OpenAPIParameter { + readonly in?: string; + readonly name?: string; + readonly schema?: { readonly type?: string }; +} + +Deno.test('createOpenAPIHandler coerces a docs-shaped numeric query parameter', async () => { + const router = os.router({ + board: os + .route({ method: 'GET', path: '/issues/board' }) + .input(z.object({ cycleId: z.number() })) + .output(z.object({ cycleId: z.number() })) + .handler(({ input }) => ({ cycleId: input.cycleId })), + }); + const handler = createOpenAPIHandler(router); + const docs = new Hono(); + docs.get( + '/api/openapi.json', + createOpenAPISpec(router, { title: 'Issue API', version: '1.0.0' }), + ); + + const specResponse = await docs.request('/api/openapi.json'); + const spec = await specResponse.json() as { + readonly paths?: Record< + string, + { readonly get?: { readonly parameters?: OpenAPIParameter[] } } + >; + }; + const cycleId = spec.paths?.['/issues/board']?.get?.parameters?.find((parameter) => + parameter.name === 'cycleId' + ); + + const result = await handler.handle( + new Request('http://localhost/api/issues/board?cycleId=1'), + { prefix: '/api' }, + ); + + assertEquals(specResponse.status, 200); + assertEquals(cycleId?.in, 'query'); + assertEquals(cycleId?.schema?.type, 'number'); + assertEquals(result.matched, true); + assertEquals(result.response?.status, 200); + assertEquals(await result.response?.json(), { cycleId: 1 }); +}); Deno.test('createNotFoundHandler returns service-scoped not found response', async () => { const app = new Hono();