From e68cb9c4bbc99bef803104c675c0443c0ee53f0b Mon Sep 17 00:00:00 2001 From: colton Date: Sat, 10 Oct 2026 21:04:41 -0700 Subject: [PATCH] contract: the unit create, configurator cap and millwork 404 match the code UnitCreate requires dimension (unit/input.go refuses a create without one); the configurator bodies are bounded by the platform's 10 MiB limit with a 413, not 64 KiB; the millwork option read answers not_found beside app_disabled. The worker package comment names its six jobs and the exposure scheduler comment names the outbox, not the in-process bus. openapi.yaml and the TypeScript types are regenerated. Co-Authored-By: Claude Opus 5.5 Signed-off-by: colton --- core/api/fragments/configurator.yaml | 8 +++++--- core/api/fragments/millwork.yaml | 11 ++++++++++- core/api/fragments/units.yaml | 4 ++-- core/api/openapi.yaml | 17 ++++++++++++----- core/internal/app/worker/worker.go | 12 +++++++----- core/internal/quote/exposure_scheduler.go | 8 ++++---- web/packages/api-client/src/schema.d.ts | 18 +++++++++++++----- 7 files changed, 53 insertions(+), 25 deletions(-) diff --git a/core/api/fragments/configurator.yaml b/core/api/fragments/configurator.yaml index ba4a792c4..58f0a03d4 100644 --- a/core/api/fragments/configurator.yaml +++ b/core/api/fragments/configurator.yaml @@ -116,8 +116,9 @@ paths: tags: [configurator] summary: Validate a set of selections against the rules description: >- - The body is capped at 64 KiB; an oversized or malformed body, or an - empty selections map, is a 400 naming the field. A rule conflict is + The body is bounded by the platform request size limit (10 MiB); a + larger body is a 413. A malformed body, or an empty selections map, + is a 400 naming the field. A rule conflict is a 200 with valid false and the conflicts listed (conflicts is always an array, never omitted). The verdict is deterministic: the same selections always answer the same conflicts in the same order. @@ -149,7 +150,8 @@ paths: tags: [configurator] summary: Build a non stock SKU from selections description: >- - The body is capped at 64 KiB. Product type and selections are both + The body is bounded by the platform request size limit (10 MiB); a + larger body is a 413. Product type and selections are both required. The selections are validated first; a conflict is a 400 validation_failed whose details carry one config_conflict blocker per violated rule (the base answered one fixed message and never said diff --git a/core/api/fragments/millwork.yaml b/core/api/fragments/millwork.yaml index 463644cba..99ce08f47 100644 --- a/core/api/fragments/millwork.yaml +++ b/core/api/fragments/millwork.yaml @@ -128,7 +128,16 @@ paths: "403": $ref: "#/components/responses/Forbidden" "404": - $ref: "#/components/responses/AppDisabled" + description: >- + The millwork app is disabled on this instance (app_disabled, + written by the registry gate), or no option has this id + (not_found). + content: + application/json: + schema: + oneOf: + - $ref: "#/components/schemas/AppsDisabledError" + - $ref: "#/components/schemas/WireError" "500": $ref: "#/components/responses/InternalErrorEither" components: diff --git a/core/api/fragments/units.yaml b/core/api/fragments/units.yaml index 028a50853..b65fa9cfd 100644 --- a/core/api/fragments/units.yaml +++ b/core/api/fragments/units.yaml @@ -231,9 +231,9 @@ components: format: date-time UnitCreate: type: object - required: [code, name] + required: [code, name, dimension] description: >- - The create body. The dimension defaults to count; the standard size + The create body. The dimension is required; the standard size is both halves or neither. is_system and revision are never accepted. properties: code: diff --git a/core/api/openapi.yaml b/core/api/openapi.yaml index 1ba988fc1..00df12cf5 100644 --- a/core/api/openapi.yaml +++ b/core/api/openapi.yaml @@ -2102,7 +2102,7 @@ paths: tags: [configurator] summary: Validate a set of selections against the rules description: >- - The body is capped at 64 KiB; an oversized or malformed body, or an empty selections map, is a 400 naming the field. A rule conflict is a 200 with valid false and the conflicts listed (conflicts is always an array, never omitted). The verdict is deterministic: the same selections always answer the same conflicts in the same order. + The body is bounded by the platform request size limit (10 MiB); a larger body is a 413. A malformed body, or an empty selections map, is a 400 naming the field. A rule conflict is a 200 with valid false and the conflicts listed (conflicts is always an array, never omitted). The verdict is deterministic: the same selections always answer the same conflicts in the same order. parameters: - $ref: "#/components/parameters/IdempotencyKey" requestBody: @@ -2131,7 +2131,7 @@ paths: tags: [configurator] summary: Build a non stock SKU from selections description: >- - The body is capped at 64 KiB. Product type and selections are both required. The selections are validated first; a conflict is a 400 validation_failed whose details carry one config_conflict blocker per violated rule (the base answered one fixed message and never said which rules). The SKU is NS, the product type code, then the Species, Grade, Treatment and Dimensions selections upper cased with spaces as hyphens; the keys other than those four are ignored. + The body is bounded by the platform request size limit (10 MiB); a larger body is a 413. Product type and selections are both required. The selections are validated first; a conflict is a 400 validation_failed whose details carry one config_conflict blocker per violated rule (the base answered one fixed message and never said which rules). The SKU is NS, the product type code, then the Species, Grade, Treatment and Dimensions selections upper cased with spaces as hyphens; the keys other than those four are ignored. parameters: - $ref: "#/components/parameters/IdempotencyKey" requestBody: @@ -8028,7 +8028,14 @@ paths: "403": $ref: "#/components/responses/Forbidden" "404": - $ref: "#/components/responses/AppDisabled" + description: >- + The millwork app is disabled on this instance (app_disabled, written by the registry gate), or no option has this id (not_found). + content: + application/json: + schema: + oneOf: + - $ref: "#/components/schemas/AppsDisabledError" + - $ref: "#/components/schemas/WireError" "500": $ref: "#/components/responses/InternalErrorEither" /api/v1/orders: @@ -26804,9 +26811,9 @@ components: format: date-time UnitCreate: type: object - required: [code, name] + required: [code, name, dimension] description: >- - The create body. The dimension defaults to count; the standard size is both halves or neither. is_system and revision are never accepted. + The create body. The dimension is required; the standard size is both halves or neither. is_system and revision are never accepted. properties: code: type: string diff --git a/core/internal/app/worker/worker.go b/core/internal/app/worker/worker.go index d7fc33367..031b0c8a3 100644 --- a/core/internal/app/worker/worker.go +++ b/core/internal/app/worker/worker.go @@ -2,11 +2,13 @@ // SPDX-FileCopyrightText: 2026 FutureBuild, Inc. and OpenLBM contributors // Package worker is the worker role of the core binary: the background -// jobs that do not belong to the HTTP server. Today that is the -// idempotency retention purge, which R1-4 moved out of serve, the -// outbox drain (R1-12), which delivers committed events to their -// subscribers, and the outbox retention purge (R1-12b); the cron schedulers that serve mounts beside its own HTTP -// surface (auto-reorder and scheduled report delivery) stay in serve. +// jobs that do not belong to the HTTP server. Today those are six: the +// idempotency retention purge, which R1-4 moved out of serve; the outbox +// drain (R1-12), which delivers committed events to their subscribers; the +// outbox retention purge (R1-12b); the draft events purge; and the order +// module's allocation and fulfilment queue jobs. The cron schedulers that +// serve mounts beside its own HTTP surface (auto-reorder and scheduled +// report delivery) stay in serve. package worker import ( diff --git a/core/internal/quote/exposure_scheduler.go b/core/internal/quote/exposure_scheduler.go index 8acc543e2..8ec5f1119 100644 --- a/core/internal/quote/exposure_scheduler.go +++ b/core/internal/quote/exposure_scheduler.go @@ -64,10 +64,10 @@ func (r *dbExposureSettingsReader) Get(ctx context.Context, key string) (string, // leave a quote un-flagged until the next manual refresh. The nightly pass // closes that gap. // -// It is also the recovery path for the in-process event bus, which is -// at-most-once and drops events on restart or subscriber saturation -// (pkg/eventbus). Durable exposure state is recomputed here from Postgres, so -// a dropped notification costs latency, not correctness. Disabled by default. +// Exposure notifications travel over the transactional outbox, delivered at +// least once by the worker's drain; durable exposure state is recomputed +// here from Postgres, so a notification row parked after its retries costs +// a notification, not correctness. Disabled by default. type ExposureScheduler struct { scanner safetyNetScanner settings exposureSettingsReader diff --git a/web/packages/api-client/src/schema.d.ts b/web/packages/api-client/src/schema.d.ts index 1b6ee2954..1d905c12c 100644 --- a/web/packages/api-client/src/schema.d.ts +++ b/web/packages/api-client/src/schema.d.ts @@ -896,7 +896,7 @@ export interface paths { put?: never; /** * Validate a set of selections against the rules - * @description The body is capped at 64 KiB; an oversized or malformed body, or an empty selections map, is a 400 naming the field. A rule conflict is a 200 with valid false and the conflicts listed (conflicts is always an array, never omitted). The verdict is deterministic: the same selections always answer the same conflicts in the same order. + * @description The body is bounded by the platform request size limit (10 MiB); a larger body is a 413. A malformed body, or an empty selections map, is a 400 naming the field. A rule conflict is a 200 with valid false and the conflicts listed (conflicts is always an array, never omitted). The verdict is deterministic: the same selections always answer the same conflicts in the same order. */ post: operations["configuratorValidate"]; delete?: never; @@ -916,7 +916,7 @@ export interface paths { put?: never; /** * Build a non stock SKU from selections - * @description The body is capped at 64 KiB. Product type and selections are both required. The selections are validated first; a conflict is a 400 validation_failed whose details carry one config_conflict blocker per violated rule (the base answered one fixed message and never said which rules). The SKU is NS, the product type code, then the Species, Grade, Treatment and Dimensions selections upper cased with spaces as hyphens; the keys other than those four are ignored. + * @description The body is bounded by the platform request size limit (10 MiB); a larger body is a 413. Product type and selections are both required. The selections are validated first; a conflict is a 400 validation_failed whose details carry one config_conflict blocker per violated rule (the base answered one fixed message and never said which rules). The SKU is NS, the product type code, then the Species, Grade, Treatment and Dimensions selections upper cased with spaces as hyphens; the keys other than those four are ignored. */ post: operations["configuratorBuildSku"]; delete?: never; @@ -13603,12 +13603,12 @@ export interface components { /** Format: date-time */ updated_at: string; }; - /** @description The create body. The dimension defaults to count; the standard size is both halves or neither. is_system and revision are never accepted. */ + /** @description The create body. The dimension is required; the standard size is both halves or neither. is_system and revision are never accepted. */ UnitCreate: { code: string; name: string; /** @enum {string} */ - dimension?: "count" | "length" | "area" | "volume" | "weight" | "board_measure"; + dimension: "count" | "length" | "area" | "volume" | "weight" | "board_measure"; std_unit_qty?: components["schemas"]["Quantity"]; std_ref_qty?: components["schemas"]["Quantity"]; }; @@ -21476,7 +21476,15 @@ export interface operations { 400: components["responses"]["BadRequestEither"]; 401: components["responses"]["Unauthorized"]; 403: components["responses"]["Forbidden"]; - 404: components["responses"]["AppDisabled"]; + /** @description The millwork app is disabled on this instance (app_disabled, written by the registry gate), or no option has this id (not_found). */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AppsDisabledError"] | components["schemas"]["WireError"]; + }; + }; 500: components["responses"]["InternalErrorEither"]; }; };