From 77d63135af4206b896a1874885643b25510f3f3b Mon Sep 17 00:00:00 2001 From: James Maes Date: Thu, 15 Oct 2026 11:43:38 -0500 Subject: [PATCH 1/2] feat(ui-api): extension for QQQ UI API 1.6.0 (operationIds, BasicErrorResponseV1, QQQ 4.1.0-RC.14) Regenerated against UI API 1.6.0: every operation has an operationId and the standard error responses (BasicErrorResponseV1: error, code, message, fieldErrors, traceId); the extension's own error schema is kept but deprecated; hand-written error bodies go through QJavalinUtils so they carry code/message/traceId. Extension MINOR bumped. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 + README.md | 14 +- pom.xml | 2 +- .../com/kingsrook/qbits/edi/api/EdiApi.java | 15 +- .../kingsrook/qbits/edi/api/EdiSchemas.java | 2 +- src/main/resources/openapi/qqq-v1/edi.yaml | 210 ++++++++++++++++-- .../qbits/edi/EdiApiContractTest.java | 8 +- .../kingsrook/qbits/edi/EdiApiHttpTest.java | 14 +- 8 files changed, 223 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57c84cc..bdea3fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ Following [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and [Semantic ### Changed +- UI API extension edi 1.1.0 (requires QQQ 4.1.0-RC.14, UI API 1.6.0): operationIds, standard error responses with BasicErrorResponseV1, extension tags, and retained deprecated EdiErrorV1 schema. + - Derive qbit metadata version from the Maven artifact version, keeping release metadata and help bundle versions in sync. - Require QQQ 4.1.0-RC.7 for core help-bundle registration and coverage validation. diff --git a/README.md b/README.md index d94f868..b882c45 100644 --- a/README.md +++ b/README.md @@ -95,8 +95,8 @@ transports/guides and local certificates/identities. ## UI API -With optional Javalin present, the producer registers the `edi` extension, version `1.0.0`, -under `/qqq/v1/qbit/edi`. All routes are endpoint specs and pass strict UI-route auditing. +With optional Javalin present, the producer registers the `edi` extension, version `1.1.0`, +under `/qqq/v1/qbit/edi`, requiring QQQ 4.1.0-RC.14 (UI API 1.6.0). All routes are endpoint specs and pass strict UI-route auditing. | Method | Path after `/qqq/v1/qbit/edi` | Result | |---|---|---| @@ -133,15 +133,15 @@ The mapper saves `edi_map_rule` through the **core table API**, using its normal `edi.map_rule.insert|edit|delete` permissions and record locks. There are no custom rule-write routes. The Next mapper page is separate phase-f work. -The generated contract is `src/main/resources/openapi/qqq-v1/edi.yaml`; all component names are -`Edi...V1`. Regenerate intentionally with: +The generated contract is `src/main/resources/openapi/qqq-v1/edi.yaml`; extension component names are +`Edi...V1`, alongside core `BasicErrorResponseV1`. `EdiErrorV1` remains deprecated for compatibility. Regenerate with the locally installed migration build: ```powershell -./mvnw.cmd -B '-Dtest=EdiApiContractTest' '-Dedi.openapi.update=true' test +./mvnw.cmd -o -B '-Dqqq.snapshot.version=4.1.99-SNAPSHOT' '-Dtest=EdiApiContractTest' '-Dedi.openapi.update=true' test ``` -RC.6 provides `UiApiContract.check`, used to compare generated and checked-in YAML and run -`OpenApiCompatibilityChecker` against this initial fragment baseline. The extension also appears +Core provides `UiApiContract.check`, used to compare generated and checked-in YAML, validate +operation IDs and references, and run `OpenApiCompatibilityChecker` against the committed fragment baseline. The extension also appears in the host's `/qqq/v1/openapi.yaml` and `/qqq/v1/version`. ## Inbound operation diff --git a/pom.xml b/pom.xml index 1323f06..b227a08 100644 --- a/pom.xml +++ b/pom.xml @@ -242,7 +242,7 @@ true - 4.1.0-RC.7 + 4.1.0-RC.14 diff --git a/src/main/java/com/kingsrook/qbits/edi/api/EdiApi.java b/src/main/java/com/kingsrook/qbits/edi/api/EdiApi.java index fc75eee..47527ad 100644 --- a/src/main/java/com/kingsrook/qbits/edi/api/EdiApi.java +++ b/src/main/java/com/kingsrook/qbits/edi/api/EdiApi.java @@ -36,6 +36,7 @@ import com.kingsrook.qqq.middleware.javalin.specs.BasicResponse; import com.kingsrook.qqq.middleware.javalin.specs.extensions.AbstractJsonEndpointSpec; import com.kingsrook.qqq.middleware.javalin.specs.extensions.MiddlewareSpecExtension; import com.kingsrook.qqq.middleware.javalin.specs.extensions.MiddlewareSpecExtensionsMetaData; +import com.kingsrook.qqq.middleware.javalin.specs.v1.responses.BasicErrorResponseV1; import com.kingsrook.qqq.openapi.model.HttpMethod; import com.kingsrook.qqq.openapi.model.In; import com.kingsrook.qqq.openapi.model.Parameter; @@ -44,7 +45,7 @@ import com.kingsrook.qqq.openapi.model.Type; import io.javalin.http.HttpStatus; /******************************************************************************* - ** The EDI 1.0.0 UI API: four spec'd operations in the default qbit namespace. + ** The EDI 1.1.0 UI API: four spec'd operations in the default qbit namespace. *******************************************************************************/ public final class EdiApi { @@ -68,7 +69,7 @@ public final class EdiApi *******************************************************************************/ public static MiddlewareSpecExtension extension() { - return (new MiddlewareSpecExtension().withName("edi").withSemanticVersion("1.0.0") + return (new MiddlewareSpecExtension().withName("edi").withSemanticVersion("1.1.0") .withDescription("EDI sandbox and mapper, using the signed-in user's permissions.") .withSpec(GuideTreeSpec.class).withSpec(MapTargetSpec.class).withSpec(PreviewSpec.class).withSpec(ValidateSpec.class)); } @@ -148,11 +149,11 @@ public final class EdiApi @Override public List defineAdditionalBasicResponses() { - return (List.of(new BasicResponse(HttpStatus.BAD_REQUEST, "Invalid request or sample exceeds 5 MiB", "EdiErrorV1"), - new BasicResponse(HttpStatus.UNAUTHORIZED, "Sign in required", "EdiErrorV1"), - new BasicResponse(HttpStatus.FORBIDDEN, "Permission denied", "EdiErrorV1"), - new BasicResponse(HttpStatus.NOT_FOUND, "Missing or invisible record", "EdiErrorV1"), - new BasicResponse(HttpStatus.CONTENT_TOO_LARGE, "Host HTTP request size limit exceeded", "EdiErrorV1"))); + return (List.of(new BasicResponse(HttpStatus.BAD_REQUEST, "Invalid request or sample exceeds 5 MiB", BasicErrorResponseV1.class.getSimpleName()), + new BasicResponse(HttpStatus.UNAUTHORIZED, "Sign in required", BasicErrorResponseV1.class.getSimpleName()), + new BasicResponse(HttpStatus.FORBIDDEN, "Permission denied", BasicErrorResponseV1.class.getSimpleName()), + new BasicResponse(HttpStatus.NOT_FOUND, "Missing or invisible record", BasicErrorResponseV1.class.getSimpleName()), + new BasicResponse(HttpStatus.CONTENT_TOO_LARGE, "Host HTTP request size limit exceeded", BasicErrorResponseV1.class.getSimpleName()))); } /******************************************************************************* diff --git a/src/main/java/com/kingsrook/qbits/edi/api/EdiSchemas.java b/src/main/java/com/kingsrook/qbits/edi/api/EdiSchemas.java index f66daa0..8a7d2dd 100644 --- a/src/main/java/com/kingsrook/qbits/edi/api/EdiSchemas.java +++ b/src/main/java/com/kingsrook/qbits/edi/api/EdiSchemas.java @@ -489,7 +489,7 @@ public final class EdiSchemas @Override public Schema toSchema() { - return (new Schema().withType(Type.OBJECT).withDescription("Core middleware error body.") + return (new Schema().withType(Type.OBJECT).withDescription("Deprecated since 1.1.0: errors use BasicErrorResponseV1.") .withProperties(new TreeMap<>(Map.ofEntries( Map.entry("error", new Schema().withType(Type.STRING).withNullable(true)))))); } diff --git a/src/main/resources/openapi/qqq-v1/edi.yaml b/src/main/resources/openapi/qqq-v1/edi.yaml index 0c8cc1c..236b6c9 100644 --- a/src/main/resources/openapi/qqq-v1/edi.yaml +++ b/src/main/resources/openapi/qqq-v1/edi.yaml @@ -1,5 +1,44 @@ components: schemas: + BasicErrorResponseV1: + description: "The error body of every UI API operation (core and extensions)." + properties: + error: + description: "The error message (the same as message; kept for clients of\ + \ UI API 1.0 to 1.5)." + type: "string" + code: + description: "A stable, machine-readable code. Well-known values: BAD_REQUEST\ + \ (400), UNAUTHENTICATED (401),\nPERMISSION_DENIED (403), NOT_FOUND (404),\ + \ CONFLICT (409), RATE_LIMITED (429), INTERNAL_ERROR (500),\nERROR (other).\ + \ Extensions may add codes; treat an unknown code by the HTTP status.\ + \ Since 1.6.0." + type: "string" + message: + description: "A message to show the user. Since 1.6.0." + type: "string" + fieldErrors: + description: "Errors about particular request fields, when known. Since\ + \ 1.6.0." + items: + properties: + field: + description: "The field (or parameter) name." + type: "string" + message: + description: "The error about that field." + type: "string" + code: + description: "An optional machine-readable code, e.g. REQUIRED, TOO_LONG,\ + \ PATTERN." + type: "string" + type: "object" + type: "array" + traceId: + description: "Identifies this request in the server logs (also the X-QQQ-Trace-Id\ + \ response header); quote it when reporting a problem. Since 1.6.0." + type: "string" + type: "object" EdiAckV1: description: "Concatenated acknowledgement previews; type is 997, 999, TA1,\ \ CONTRL, NONE or MIXED. text is null for PHI." @@ -93,7 +132,7 @@ components: type: "string" type: "object" EdiErrorV1: - description: "Core middleware error body." + description: "Deprecated since 1.1.0: errors use BasicErrorResponseV1." properties: error: nullable: true @@ -397,21 +436,55 @@ components: type: "object" securitySchemes: sessionUuidCookie: + description: "The session cookie set by POST /qqq/v1/manageSession (web clients)." in: "cookie" name: "sessionUUID" type: "apiKey" + bearerAuth: + bearerFormat: "JWT or session UUID" + description: "`Authorization: Bearer `, where the token is either\n-\ + \ an OAuth2 access token (a signed JWT) issued by the app's identity provider\ + \ to the app's client,\n or to its native client (`values.nativeClientId`\ + \ in GET /qqq/v1/metaData/authentication), for\n example from the device-authorization\ + \ grant or auth-code + PKCE. It is verified against the\n provider's keys\ + \ (issuer, audience, expiry, signature) and gives the same user and permissions\n\ + \ as signing in on the web. When it expires (401), refresh it at the provider\ + \ and retry; or\n- a session UUID returned by POST /qqq/v1/manageSession,\ + \ which the server keeps refreshed." + scheme: "bearer" + type: "http" + oauth2: + description: "The app's OAuth2 / OpenID Connect provider. The real endpoints\ + \ come from the provider's discovery\ndocument: `{values.baseUrl}.well-known/openid-configuration`,\ + \ with values from\nGET /qqq/v1/metaData/authentication. Native clients use\ + \ `values.nativeClientId` (a public client)\nwith auth-code + PKCE (loopback\ + \ or custom-scheme redirect URI registered at the provider) or the\ndevice-authorization\ + \ grant (`device_authorization_endpoint`, RFC 8628), then send the access\ + \ token\nas `Authorization: Bearer`." + flows: + authorizationCode: + authorizationUrl: "/authorize" + scopes: + email: "Email address" + offline_access: "A refresh token, to renew the access token without signing\ + \ in again" + openid: "OpenID Connect sign-in" + profile: "Name, username and groups (permissions come from the groups)" + tokenUrl: "/token" + type: "oauth2" info: contact: email: "contact@kingsrook.com" description: "EDI sandbox and mapper, using the signed-in user's permissions." title: "QQQ Middleware API extension: edi" - version: "1.0.0" + version: "1.1.0" openapi: "3.0.3" paths: /qqq/v1/qbit/edi/guides/{guideId}/tree: get: description: "Read the guide tree. Uses the caller session and table read permissions.\ \ Preview also requires edi.map.preview. Rule writes use core table CRUD." + operationId: "getQbitEdiGuidesByGuideIdTree" parameters: - description: "Positive record identifier." in: "path" @@ -424,25 +497,25 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/EdiErrorV1" + $ref: "#/components/schemas/BasicErrorResponseV1" description: "Invalid request or sample exceeds 5 MiB" 401: content: application/json: schema: - $ref: "#/components/schemas/EdiErrorV1" + $ref: "#/components/schemas/BasicErrorResponseV1" description: "Sign in required" 403: content: application/json: schema: - $ref: "#/components/schemas/EdiErrorV1" + $ref: "#/components/schemas/BasicErrorResponseV1" description: "Permission denied" 404: content: application/json: schema: - $ref: "#/components/schemas/EdiErrorV1" + $ref: "#/components/schemas/BasicErrorResponseV1" description: "Missing or invisible record" 200: content: @@ -454,17 +527,39 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/EdiErrorV1" + $ref: "#/components/schemas/BasicErrorResponseV1" description: "Host HTTP request size limit exceeded" + 409: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Conflict with the current state, e.g. an edit conflict or\ + \ an invalid transition (code CONFLICT)." + 429: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Too many requests (code RATE_LIMITED); retry later." security: - sessionUuidCookie: - "N/A" + - bearerAuth: + - "N/A" + - oauth2: + - "openid" + - "profile" + - "email" summary: "Read the guide tree" + tags: + - "edi" /qqq/v1/qbit/edi/maps/{mapId}/target: get: description: "Read target metadata and map rules. Uses the caller session and\ \ table read permissions. Preview also requires edi.map.preview. Rule writes\ \ use core table CRUD." + operationId: "getQbitEdiMapsByMapIdTarget" parameters: - description: "Positive record identifier." in: "path" @@ -477,25 +572,25 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/EdiErrorV1" + $ref: "#/components/schemas/BasicErrorResponseV1" description: "Invalid request or sample exceeds 5 MiB" 401: content: application/json: schema: - $ref: "#/components/schemas/EdiErrorV1" + $ref: "#/components/schemas/BasicErrorResponseV1" description: "Sign in required" 403: content: application/json: schema: - $ref: "#/components/schemas/EdiErrorV1" + $ref: "#/components/schemas/BasicErrorResponseV1" description: "Permission denied" 404: content: application/json: schema: - $ref: "#/components/schemas/EdiErrorV1" + $ref: "#/components/schemas/BasicErrorResponseV1" description: "Missing or invisible record" 200: content: @@ -507,17 +602,39 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/EdiErrorV1" + $ref: "#/components/schemas/BasicErrorResponseV1" description: "Host HTTP request size limit exceeded" + 409: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Conflict with the current state, e.g. an edit conflict or\ + \ an invalid transition (code CONFLICT)." + 429: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Too many requests (code RATE_LIMITED); retry later." security: - sessionUuidCookie: - "N/A" + - bearerAuth: + - "N/A" + - oauth2: + - "openid" + - "profile" + - "email" summary: "Read target metadata and map rules" + tags: + - "edi" /qqq/v1/qbit/edi/sandbox/preview: post: description: "Preview an encrypted sample with optional unsaved rules. Uses\ \ the caller session and table read permissions. Preview also requires edi.map.preview.\ \ Rule writes use core table CRUD." + operationId: "postQbitEdiSandboxPreview" requestBody: content: application/json: @@ -529,25 +646,25 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/EdiErrorV1" + $ref: "#/components/schemas/BasicErrorResponseV1" description: "Invalid request or sample exceeds 5 MiB" 401: content: application/json: schema: - $ref: "#/components/schemas/EdiErrorV1" + $ref: "#/components/schemas/BasicErrorResponseV1" description: "Sign in required" 403: content: application/json: schema: - $ref: "#/components/schemas/EdiErrorV1" + $ref: "#/components/schemas/BasicErrorResponseV1" description: "Permission denied" 404: content: application/json: schema: - $ref: "#/components/schemas/EdiErrorV1" + $ref: "#/components/schemas/BasicErrorResponseV1" description: "Missing or invisible record" 200: content: @@ -559,17 +676,39 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/EdiErrorV1" + $ref: "#/components/schemas/BasicErrorResponseV1" description: "Host HTTP request size limit exceeded" + 409: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Conflict with the current state, e.g. an edit conflict or\ + \ an invalid transition (code CONFLICT)." + 429: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Too many requests (code RATE_LIMITED); retry later." security: - sessionUuidCookie: - "N/A" + - bearerAuth: + - "N/A" + - oauth2: + - "openid" + - "profile" + - "email" summary: "Preview an encrypted sample with optional unsaved rules" + tags: + - "edi" /qqq/v1/qbit/edi/maps/{mapId}/validate: post: description: "Validate unsaved map rules. Uses the caller session and table\ \ read permissions. Preview also requires edi.map.preview. Rule writes use\ \ core table CRUD." + operationId: "postQbitEdiMapsByMapIdValidate" parameters: - description: "Positive record identifier." in: "path" @@ -588,25 +727,25 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/EdiErrorV1" + $ref: "#/components/schemas/BasicErrorResponseV1" description: "Invalid request or sample exceeds 5 MiB" 401: content: application/json: schema: - $ref: "#/components/schemas/EdiErrorV1" + $ref: "#/components/schemas/BasicErrorResponseV1" description: "Sign in required" 403: content: application/json: schema: - $ref: "#/components/schemas/EdiErrorV1" + $ref: "#/components/schemas/BasicErrorResponseV1" description: "Permission denied" 404: content: application/json: schema: - $ref: "#/components/schemas/EdiErrorV1" + $ref: "#/components/schemas/BasicErrorResponseV1" description: "Missing or invisible record" 200: content: @@ -618,9 +757,36 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/EdiErrorV1" + $ref: "#/components/schemas/BasicErrorResponseV1" description: "Host HTTP request size limit exceeded" + 409: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Conflict with the current state, e.g. an edit conflict or\ + \ an invalid transition (code CONFLICT)." + 429: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Too many requests (code RATE_LIMITED); retry later." security: - sessionUuidCookie: - "N/A" + - bearerAuth: + - "N/A" + - oauth2: + - "openid" + - "profile" + - "email" summary: "Validate unsaved map rules" + tags: + - "edi" +servers: +- description: "The QQQ server that serves this document (paths include /qqq/v1)." + url: "/" +tags: +- description: "Operations of the edi extension." + name: "edi" diff --git a/src/test/java/com/kingsrook/qbits/edi/EdiApiContractTest.java b/src/test/java/com/kingsrook/qbits/edi/EdiApiContractTest.java index 27b339e..df90d57 100644 --- a/src/test/java/com/kingsrook/qbits/edi/EdiApiContractTest.java +++ b/src/test/java/com/kingsrook/qbits/edi/EdiApiContractTest.java @@ -35,7 +35,7 @@ import static org.assertj.core.api.Assertions.assertThat; class EdiApiContractTest { /******************************************************************************* - ** Use RC.6's real helper, including compatibility comparison against this first baseline. + ** Check the generated contract and compatibility against the committed baseline. *******************************************************************************/ @Test void contract() throws Exception @@ -53,15 +53,15 @@ class EdiApiContractTest assertThat(UiApiContract.check(generated, committed, committed, 1, "mvnw.cmd -B -Dtest=EdiApiContractTest -Dedi.openapi.update=true test")).isEmpty(); var model = version.generateExtensionOpenAPIModel("qqq", extension); - assertThat(model.getComponents().getSchemas().keySet()).allMatch(n -> n.startsWith("Edi") && n.endsWith("V1")); + assertThat(model.getComponents().getSchemas().keySet()).allMatch(n -> (n.startsWith("Edi") && n.endsWith("V1")) || n.equals("BasicErrorResponseV1")); assertThat(model.getPaths()).hasSize(4); assertThat(model.getPaths().keySet()).allMatch(n -> n.startsWith("/qqq/v1/qbit/edi/")); assertThat(extension.getEffectivePathPrefix()).isEqualTo("/qbit/edi"); - assertThat(extension.getSemanticVersion()).isEqualTo("1.0.0"); + assertThat(extension.getSemanticVersion()).isEqualTo("1.1.0"); } /******************************************************************************* - ** Producer registers the extension and all metadata validates with RC.6. + ** Producer registers the extension and all metadata validates with core middleware. *******************************************************************************/ @Test void registration() throws Exception diff --git a/src/test/java/com/kingsrook/qbits/edi/EdiApiHttpTest.java b/src/test/java/com/kingsrook/qbits/edi/EdiApiHttpTest.java index 412466c..70b2dbc 100644 --- a/src/test/java/com/kingsrook/qbits/edi/EdiApiHttpTest.java +++ b/src/test/java/com/kingsrook/qbits/edi/EdiApiHttpTest.java @@ -169,8 +169,18 @@ class EdiApiHttpTest private HttpResponse request(String method, String path, Object body, String role) throws Exception { String text = body == null ? "" : body instanceof String s ? s : JSON.writeValueAsString(body); - return (client.send(HttpRequest.newBuilder(URI.create(baseUrl + path)).header("Cookie", "sessionUUID=" + role + suffix) - .header("Content-Type", "application/json").method(method, HttpRequest.BodyPublishers.ofString(text)).build(), HttpResponse.BodyHandlers.ofString())); + var response = client.send(HttpRequest.newBuilder(URI.create(baseUrl + path)).header("Cookie", "sessionUUID=" + role + suffix) + .header("Content-Type", "application/json").method(method, HttpRequest.BodyPublishers.ofString(text)).build(), HttpResponse.BodyHandlers.ofString()); + if(response.statusCode() >= 400) + { + JsonNode error = JSON.readTree(response.body()); + for(String field : List.of("error", "code", "message", "traceId")) + { + assertThat(error.path(field).isTextual()).as(field + " in " + response.body()).isTrue(); + assertThat(error.path(field).asText()).isNotBlank(); + } + } + return (response); } /******************************************************************************* -- 2.54.0 From 36f1b939b47ddb8c57a86eebc916e4a3d94be44a Mon Sep 17 00:00:00 2001 From: James Maes Date: Thu, 15 Oct 2026 12:27:06 -0500 Subject: [PATCH 2/2] chore: target QQQ 4.1.0-RC.15 (UI API 1.6.0) Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 +- README.md | 2 +- pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bdea3fb..9e8e485 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ Following [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and [Semantic ### Changed -- UI API extension edi 1.1.0 (requires QQQ 4.1.0-RC.14, UI API 1.6.0): operationIds, standard error responses with BasicErrorResponseV1, extension tags, and retained deprecated EdiErrorV1 schema. +- UI API extension edi 1.1.0 (requires QQQ 4.1.0-RC.15, UI API 1.6.0): operationIds, standard error responses with BasicErrorResponseV1, extension tags, and retained deprecated EdiErrorV1 schema. - Derive qbit metadata version from the Maven artifact version, keeping release metadata and help bundle versions in sync. diff --git a/README.md b/README.md index b882c45..d031317 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ transports/guides and local certificates/identities. ## UI API With optional Javalin present, the producer registers the `edi` extension, version `1.1.0`, -under `/qqq/v1/qbit/edi`, requiring QQQ 4.1.0-RC.14 (UI API 1.6.0). All routes are endpoint specs and pass strict UI-route auditing. +under `/qqq/v1/qbit/edi`, requiring QQQ 4.1.0-RC.15 (UI API 1.6.0). All routes are endpoint specs and pass strict UI-route auditing. | Method | Path after `/qqq/v1/qbit/edi` | Result | |---|---|---| diff --git a/pom.xml b/pom.xml index b227a08..ffb0a24 100644 --- a/pom.xml +++ b/pom.xml @@ -242,7 +242,7 @@ true - 4.1.0-RC.14 + 4.1.0-RC.15 -- 2.54.0