fix(openapi): recognize bare object type + document existing field on validate (#1532)

* fix(openapi): recognize bare `object` type in annotation parser

The @openapi annotation parser's parseMiniSchema() only recognized the
bare words integer/number/boolean/string as scalar types. Any other
bare word - including `object`, used in 16 route annotations across
the codebase (e.g. severity:object, envVars:object on
POST /api/stacks/{name}/validate) - silently fell through to the
generic string fallback, so these fields were documented in
static/openapi.json as {"type": "string"} instead of
{"type": "object"}.

Add a dedicated branch for bare `object`, producing an opaque object
schema (no declared properties, since the bare word carries no shape
information of its own - a documented shape should use the existing
{...} form).

Verified no other bare non-standard word is used in any body:/resp-*:
annotation line across the 257 route files, so this closes the gap
completely for the current codebase.

Fixes #1531

* docs(openapi): document the existing field on POST /api/stacks/{name}/validate

The handler reads body.existing (an optional boolean, undocumented)
to control self-exclusion of the stack's own running containers/ports
from the cross-stack collision checks - the exact case the endpoint's
own summary advertises as handled. Without it, a client built strictly
from the OpenAPI document has no way to learn the field exists, and
will validate an existing stack the same way as a new one, silently
reintroducing false-positive collision findings against the stack's
own containers.

Add existing:boolean to the body: annotation, plus a one-line
description explaining the true/omitted semantics.

Fixes #1530

---------

Co-authored-by: Jarek Krochmalski <jotka@users.noreply.github.com>
This commit is contained in:
Björn StrausmannandJarek Krochmalski authored and GitHub committed 2026-09-06 15:51:12 +05:30
1 parent 360c458144
commit 33c0c47855
3 files changed
+32 -2

No files matched your search

+11 -1
View File
@@ -209,7 +209,8 @@ export function discoverRoutes(rootDirs: string[], routesRoot: string): { routes
// * resp-<code>-example: <json>
// */
//
// mini-schema := 'string'|'integer'|'number'|'boolean' | '{' (name':'type'!'?','?)* '}' | 'array<' type '>'
// mini-schema := 'string'|'integer'|'number'|'boolean'|'object' | '{' (name':'type'!'?','?)* '}' | 'array<' type '>'
// (bare 'object' = an opaque object with no declared properties; use '{...}' for a documented shape)
export function parseMiniSchema(str: string): MiniSchema {
let i = 0;
@@ -244,6 +245,15 @@ export function parseMiniSchema(str: string): MiniSchema {
if (i === start) i++;
const word = s.slice(start, i) || 'string';
if (word === 'integer' || word === 'number' || word === 'boolean' || word === 'string') return { kind: word };
// Bare `object` (no `{...}` shape given) — an opaque/untyped object, used
// throughout the codebase for fields whose internal shape isn't part of
// the documented contract (e.g. `severity:object`, `envVars:object`,
// `config:object`). Without this branch it silently fell through to the
// generic `string` fallback below, so every one of these fields was
// documented in static/openapi.json as `{ "type": "string" }` instead of
// `{ "type": "object" }` — wrong for any client generated strictly from
// the spec (a typed SDK, an MCP server mirroring the schema, ...).
if (word === 'object') return { kind: 'object', properties: {}, required: [] };
return { kind: 'string' };
}