-
Notifications
You must be signed in to change notification settings - Fork 0
feat(spec): cover 158 live gateway capabilities missing from openapi.yaml #73
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| [ | ||
| { | ||
| "name": "internal", | ||
| "justification": "Live skill has pricing.model=free and auth.scope=null — an operator-only utility route by the gateway's own metadata (no scope to grant, nothing to meter), not a customer-facing capability. Documenting it as a public operation would misrepresent it as purchasable." | ||
| } | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| #!/usr/bin/env node | ||
| /** | ||
| * skills-index-coverage.mjs — fail if a live, priced gateway capability has no operation in | ||
| * openapi.yaml. | ||
| * | ||
| * WHY THIS EXISTS: docs are GENERATED from openapi.yaml, but the gateway's live capability | ||
| * index (https://gateway.wave.online/.well-known/wave-skills.json) is the actual source of | ||
| * truth for what customers can call and pay for. The two can drift — a capability ships at | ||
| * the gateway before anyone documents it here. This gate catches that drift going forward: | ||
| * every name in the live index must resolve to a documented product in the spec (a matching | ||
| * top-level path segment or tag), or be named — with a reason — in the allowlist next to | ||
| * this script. | ||
| * | ||
| * Matching is deliberately coarse (product-level, not per-operation): the skills index itself | ||
| * is flat (one entry per product, e.g. `/v1/render`), while the spec documents richer nested | ||
| * shapes for the same product (`/render`, `/render/{jobId}`, `/render/{jobId}/events`). A skill | ||
| * named `foo` is "covered" if the spec has a path whose first segment is `foo` OR a tag whose | ||
| * name normalizes to `foo` (case/space/hyphen-insensitive). | ||
| * | ||
| * Usage: node .github/scripts/skills-index-coverage.mjs openapi.yaml | ||
| * Exit 0 = every live capability is covered or allowlisted. | ||
| * Exit 1 = at least one live, non-allowlisted capability has no matching operation. | ||
| * Exit 2 = could not read/parse the spec, or the live index could not be fetched. | ||
| * | ||
| * Network: fetches ONLY the well-known skills index URL below (GET, unauthenticated, public). | ||
| */ | ||
| import { readFileSync } from 'node:fs'; | ||
| import { fileURLToPath } from 'node:url'; | ||
| import { dirname, join } from 'node:path'; | ||
|
|
||
| const SKILLS_INDEX_URL = 'https://gateway.wave.online/.well-known/wave-skills.json'; | ||
| const __dirname = dirname(fileURLToPath(import.meta.url)); | ||
| const ALLOWLIST_PATH = join(__dirname, 'skills-index-allowlist.json'); | ||
|
|
||
| const specPath = process.argv[2]; | ||
| if (!specPath) { | ||
| console.error('usage: skills-index-coverage.mjs <openapi.yaml>'); | ||
| process.exit(2); | ||
| } | ||
|
|
||
| function norm(name) { | ||
| return String(name).replace(/[-_ ]/g, '').toLowerCase(); | ||
| } | ||
|
|
||
| let doc; | ||
| try { | ||
| const raw = readFileSync(specPath, 'utf8'); | ||
| const yaml = await import('js-yaml'); | ||
| doc = (yaml.default ?? yaml).load(raw); | ||
| } catch (err) { | ||
| console.error(`could not read/parse ${specPath}: ${err.message}`); | ||
| process.exit(2); | ||
| } | ||
|
|
||
| let allowlist = []; | ||
| try { | ||
| allowlist = JSON.parse(readFileSync(ALLOWLIST_PATH, 'utf8')); | ||
| } catch (err) { | ||
| console.error(`could not read/parse ${ALLOWLIST_PATH}: ${err.message}`); | ||
| process.exit(2); | ||
| } | ||
| const allowSet = new Set(allowlist.map((e) => norm(e.name))); | ||
| for (const e of allowlist) { | ||
| if (!e.name || !e.justification) { | ||
| console.error(`allowlist entry missing name/justification: ${JSON.stringify(e)}`); | ||
| process.exit(2); | ||
| } | ||
| } | ||
|
|
||
| const covered = new Set(); | ||
| for (const p of Object.keys(doc.paths ?? {})) { | ||
| const seg = p.split('/').filter(Boolean)[0]; | ||
| if (seg && !seg.startsWith('{')) covered.add(norm(seg)); | ||
| } | ||
| for (const t of doc.tags ?? []) { | ||
| if (t.name) covered.add(norm(t.name)); | ||
| } | ||
|
|
||
| let skills; | ||
| try { | ||
| const res = await fetch(SKILLS_INDEX_URL); | ||
| if (!res.ok) throw new Error(`HTTP ${res.status}`); | ||
| skills = await res.json(); | ||
| } catch (err) { | ||
| console.error(`could not fetch ${SKILLS_INDEX_URL}: ${err.message}`); | ||
| process.exit(2); | ||
| } | ||
|
|
||
| const missing = []; | ||
| for (const s of skills) { | ||
| const key = norm(s.name); | ||
|
Comment on lines
+90
to
+91
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
payload="$(curl -fsS --max-time 20 \
https://gateway.wave.online/.well-known/wave-skills.json)"
jq -e '
type == "array" and
all(.[]; type == "object" and (.name | type == "string") and (.name | length > 0))
' <<<"$payload"Repository: wave-av/api-spec Length of output: 159 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- repository conventions ---'
find /tmp/coderabbit-repo-knowledge/wave-av-api-spec-136a5465 -mindepth 2 -maxdepth 2 -type f -name '*.md' -print \
-exec sh -c 'echo "--- $1"; cat "$1"' sh {} \;
printf '%s\n' '--- target script ---'
cat -n .github/scripts/skills-index-coverage.mjs | sed -n '1,130p'
printf '%s\n' '--- relevant diff ---'
git diff --unified=20 1f0cb94f4a06085a594ad52269ffcb1c9dc25f83 252781ac0e20881bcccfedc604c095b0a955c661 -- .github/scripts/skills-index-coverage.mjsRepository: wave-av/api-spec Length of output: 10248 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
cat -n .github/scripts/skills-index-coverage.mjs | sed -n '1,130p'Repository: wave-av/api-spec Length of output: 4746 Validate the live index shape before iterating. If 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
| if (covered.has(key)) continue; | ||
| if (allowSet.has(key)) continue; | ||
|
Comment on lines
+79
to
+93
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| missing.push(s.name); | ||
| } | ||
|
|
||
| console.log(`skills-index-coverage: ${skills.length - missing.length}/${skills.length} live capabilities covered (${allowlist.length} allowlisted)`); | ||
| if (missing.length) { | ||
| console.error(`::error::${missing.length} live priced capabilities have no matching operation/tag in ${specPath}: ${missing.join(', ')}`); | ||
| process.exit(1); | ||
| } | ||
| console.log('skills-index-coverage: OK'); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,46 @@ All notable changes to this project are documented here. The format is based on | |
|
|
||
| ## [Unreleased] | ||
|
|
||
| ### Added | ||
|
|
||
| - **Spec coverage from the live gateway skills index** (v1.1.0). Diffed the live capability | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Use Conventional Commit titles for the new entries. The added titles do not include type prefixes. Apply prefixes such as As per coding guidelines: 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| index (`https://gateway.wave.online/.well-known/wave-skills.json`, 178 priced capabilities) | ||
| against this spec's 72 operations and added a draft operation for every capability that had | ||
| none: 158 new `POST /{name}` operations (one per missing product), 157 new tags, and a | ||
| `bearerWithScopes` OAuth2 security scheme carrying one scope per capability, all drawn | ||
| verbatim from the live index (no invented fields). Each new operation carries: | ||
| - `x-schema-status: draft` — the request/response shape is `additionalProperties: true` | ||
| because the actual payload contract is not published anywhere the spec can read it. | ||
| - `x-skill-url` — the capability's own skill document. | ||
| - `x-price` — `model`/`currency`/`network`/`meter` from the live index, plus, where an | ||
| unauthenticated `GET` on the route returned a real x402 402 challenge, the observed | ||
| `atomicAmount` and `asset` (verified live 2026-09-02; most capabilities gate at a flat | ||
| 1000-atomic-unit entry price, one at 600000 — these are the gateway's real numbers, not | ||
| estimates). | ||
| - Coverage: **before 21/178 → after 178/178** (1 allowlisted: `internal`, which the live | ||
| index itself marks `pricing.model=free` and `auth.scope=null`). | ||
|
|
||
| - **`skills-index-coverage` CI check** (`.github/scripts/skills-index-coverage.mjs`, | ||
| `.github/scripts/skills-index-allowlist.json`) — fetches the live skills index on every PR | ||
| and push to `main` and fails if a live, non-allowlisted priced capability has no matching | ||
| path segment or tag in `openapi.yaml`. Wired into `foundation-gate.yml`. | ||
|
|
||
| ### Deprecated | ||
|
|
||
| - **`GET/POST /videos/{videoId}/chapters` and `POST /videos/{videoId}/chapters/detect`** — | ||
| marked `deprecated: true` / `x-status: unrouted`. Verified live 2026-09-02: the gateway | ||
| returns `403 ROUTE_NOT_MAPPED` ("this path and method are not part of the WAVE API") for | ||
| both paths. The live-priced Chapters capability is the flat `POST /chapters` operation | ||
| (added above); the nested shape stays documented — deprecated rather than deleted — until | ||
| it is either wired up or formally removed. | ||
|
|
||
| - **`/leaderboard` and `/platform`** — confirmed live 2026-09-02: both return | ||
| `403 ROUTE_NOT_MAPPED` at the gateway. Neither appears in this spec (never did) nor in the | ||
| live gateway skills index (not a priced capability), so nothing here needed a | ||
| `deprecated: true` marker — they are documented on the publicly served `openapi.json` at | ||
| the API host but are not real operations. Recommend the publicly served copy drop them; | ||
| out of scope for this spec since they were never present here. | ||
|
|
||
| ### Fixed | ||
|
|
||
| - `pr-agent` lane: fork-triggered `/` commands are now refused, and the AI | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Edge Case: No timeout on skills-index fetch() in CI gate
fetch(SKILLS_INDEX_URL) at .github/scripts/skills-index-coverage.mjs:81 has no AbortController/timeout, so if gateway.wave.online is slow or hangs, the job blocks until the workflow's 10-minute timeout-minutes in foundation-gate.yml kills it, burning the full CI budget on every PR instead of failing fast with a clear timeout error. Add a short fetch timeout (e.g. AbortSignal.timeout(15000)) so a slow/unreachable dependency fails quickly with an actionable message.
Bound the network call so a hung gateway fails fast instead of consuming the full job timeout.:
Check the box to apply the fix or reply for a change | Was this helpful? React with 👍 / 👎