.gitea/workflows/release.yaml replaces the orb publish and the GitHub release: the scripts/release.mjs gate, Linux + darwin-x64 binaries in the container, darwin-arm64 and windows-x64 built and smoke-tested on the native runners, a draft Gitea release, SHA256SUMS checked against the published downloads, the four packages to the QRun-IO Gitea npm registry (an existing version is skipped, each package's own version picks next/latest), and for final versions the Homebrew tap and Scoop bucket (pushed when PACKAGES_TOKEN can write them, else the filled files are attached to the release). release.mjs accepts its tag when it points at HEAD and prints the new path; install.sh, update-packaging, the formula and the manifest use Gitea downloads under v<version>; publishConfig points at the Gitea registry; build.yaml now runs on branches only; the runbook describes the new path. Versions: cli and core 1.0.1-rc.1, client 1.8.1-rc.1 (content changed since 1.0.0), conformance 1.6.0 unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@qrun-io/qqq-ui-api-conformance
A black-box, read-only conformance suite for servers of the QQQ UI API
(/qqq/v1), and qqq-dev-idp, a local OpenID Connect provider for testing native-client sign-in
without an identity-provider account.
Install
Published to GitHub Packages (a GitHub token with read:packages is needed even for public packages):
echo "@qrun-io:registry=https://npm.pkg.github.com" >> ~/.npmrc
npm install -g @qrun-io/qqq-ui-api-conformance # or npx, or as a dev dependency
Node.js 20 or later. Two commands: qqq-ui-api-conformance and qqq-dev-idp.
Check a server
qqq-ui-api-conformance --server http://localhost:8077 # public checks; MOCK servers run all
qqq-ui-api-conformance --server http://localhost:8077 --token "$QQQ_TOKEN" # all read-only checks
qqq-ui-api-conformance --server http://localhost:8077 --table person --json # a chosen table, JSON report
qqq-ui-api-conformance --server http://127.0.0.1:4010 # the Prism mock (npm run mock)
--server defaults to QQQ_SERVER and --token (an OAuth2 access token or a session UUID) to
QQQ_TOKEN. Every authenticated check authenticates the same way, as the CLI does: a JWT as a bearer token,
a session UUID also as the sessionUUID cookie, and on MOCK and FULLY_ANONYMOUS servers without a token a
server session from manageSession. The exit code is 1 when any check fails, 2 on a usage error. Against the QQQ sample server:
PASS GET /qqq/v1/metaData/authentication validates (type MOCK)
SKIP a request without credentials answers 401 with the error schema (authentication type MOCK)
PASS POST /qqq/v1/table/{tableName}/query validates (2 records)
PASS X-Request-Id is echoed as the error traceId (UI API >= 1.6.0)
SKIP GET /qqq/v1/jobs validates (jobs are not enabled on this server (no durable jobs tables))
PASS GET /qqq/v1/realtime/stream opens with a hello event
...
14 passed, 3 skipped, 0 failed
What it checks, without writing anything:
GET /qqq/v1/versionanswers a 1.x semantic version; the servedopenapi.jsonis the merged document, every operation has a uniqueoperationId(1.6.0+), every$refresolves, and every extension in/versionhas operations in the document.- Authentication metadata validates; requests without credentials and with a forged bearer token answer 401 with the error schema (skipped on MOCK and anonymous servers).
- With credentials:
GET /metaData, a table's metadata (withclientCapabilitiesand field validation, 1.6.0+), query, count and get of a record validate against the document's schemas; an unknown table answers 404 with the error schema;X-Request-Idcomes back as the errortraceId(1.6.0+);GET /jobsvalidates when the server has durable jobs; the realtime stream opens withhello.
Responses are validated with Ajv against the schemas of the document the server serves. Checks that do not apply (an older server, a missing feature) are reported as SKIP with the reason, not as failures.
Use it from code
import { runConformance } from '@qrun-io/qqq-ui-api-conformance'
const results = await runConformance({ baseUrl: 'http://localhost:8077', table: 'person' })
const failed = results.filter((r) => !r.ok)
console.log(`${results.length - failed.length} ok, ${failed.length} failed`)
runConformance(options) resolves with { name, ok, skipped?, detail? } per check. Options: baseUrl,
token, table, fetch, log (called with each result as it completes). SchemaValidator is
exported for validating payloads against the document yourself.
qqq-dev-idp: a local OIDC provider
For local development and tests only: it approves sign-ins for one configured user without a password. Never expose it.
qqq-dev-idp --port 9400 --client sample-app --client sample-cli --user dev@example.com --groups admins
qqq-dev-idp --auto-approve # approve device codes and authorization requests at once (CI)
Each client gets its own issuer, http://localhost:9400/application/o/<client>/, with OIDC discovery and
JWKS (RS256 JWTs). It supports the device-authorization grant (approve a code at
GET /device?code=...), the authorization code with S256 PKCE on loopback redirects (127.0.0.1 or
localhost, any port) with an Approve / Deny page, refresh tokens, revocation and end-session. Options:
--port (9400), --client (repeatable; default sample-app and sample-cli), --user, --name,
--groups (comma-separated), --token-seconds (600), --auto-approve.
Point a local QQQ server's OAUTH2 authentication at that issuer, and qqq login (or any client using
@qrun-io/qqq-ui-api-client's deviceSignIn) runs the whole native-client flow.
TRY-IT.md has the recipe for the sample server.
In tests, startDevIdp(options) from @qrun-io/qqq-ui-api-conformance/dist/dev-idp.js starts it
in-process and returns { issuer(clientId), approve(userCode), close() }.
Versioning
The suite has its own version (1.6.0 for this release, the UI API version whose rules it checks). It
accepts servers on UI API 1.x and gates newer checks on the server's version. Released together with the
qqq CLI from the qqq-cli repository;
CHANGELOG,
issues.
Apache-2.0