Summary
Operations whose parameters are typed by a oneOf union of branded key schemas (e.g. ResourceKey) cannot be satisfied by the planner, even though producers exist for every branch. The extractor records the parameter with a synthetic semantic type derived from the ref name, no edges are emitted, the scenario is marked unsatisfied, and the value provider falls back to a non-compliant placeholder (<paramName>Var-<rand>). The server rejects with 400 Bad Request — Failed to convert ....
Concrete failing case from the live-cluster run on PR #329:
getResourceContent.feature.spec.ts › getResourceContent › feature-1 - base
Response body: {"type":"about:blank","title":"Bad Request","status":400,
"detail":"Failed to convert 'resourceKey' with value: 'resourceKeyVar-6z7zvt'",
"instance":"/v2/resources/resourceKeyVar-6z7zvt"}
This is a class-of-defect, not an instance — every operation taking a ResourceKey (or any future oneOf-of-branded-keys parameter) is broken the same way. The two related root causes are summarised below.
Spec shape
# components/schemas/ResourceKey
type: string
format: ResourceKey
oneOf:
- $ref: '#/components/schemas/ProcessDefinitionKey'
- $ref: '#/components/schemas/DecisionRequirementsKey'
- $ref: '#/components/schemas/FormKey'
- $ref: '#/components/schemas/DecisionDefinitionKey'
# each branch:
ProcessDefinitionKey:
type: string
format: ProcessDefinitionKey
x-semantic-type: ProcessDefinitionKey # ← only on branches
example: "2251799813686749"
allOf: [{ $ref: '#/components/schemas/LongKey' }]
LongKey:
type: string
pattern: ^-?[0-9]+$
minLength: 1
maxLength: 25
The four branches all have x-semantic-type and have producer operations (createDeployment returns each kind in its response items). ResourceKey itself carries no x-semantic-type — by design, since it is a union.
Root cause — extractor
semantic-graph-extractor/schema-analyzer.ts:767-783 — findSemanticTypeInSchema only walks allOf, not oneOf or anyOf:
private findSemanticTypeInSchema(schema: Schema): string | undefined {
if (schema['x-semantic-type']) return schema['x-semantic-type'];
if (schema.allOf) {
for (const subSchema of schema.allOf) {
if (!('$ref' in subSchema)) {
const found = this.findSemanticTypeInSchema(subSchema);
if (found) return found;
}
}
}
return undefined; // ← oneOf/anyOf never traversed
}
For the resourceKey parameter (schema: { $ref: '#/components/schemas/ResourceKey' }), this returns undefined. The fallback heuristic at schema-analyzer.ts:365-372 then kicks in:
if (!semanticType) {
const name = ref.split('/').pop();
if (name && /[A-Z]/.test(name) && /Key$/.test(name)) {
semanticType = name; // ← assigns "ResourceKey"
}
}
The parameter is recorded with semanticType: "ResourceKey" purely from the ref name. But the registry (built by extractSemanticTypesFromSchema at schema-analyzer.ts:61) only registers schemas that carry their own x-semantic-type, so ResourceKey is never registered. The graph's semanticTypes list has 41 entries (the four branch types are present) but no ResourceKey.
Root cause — graph-builder
semantic-graph-extractor/graph-builder.ts:60-83 — edges are emitted by strict string equality:
for (const produced of producedTypes) {
for (const consumed of consumedTypes) {
if (produced.semanticType === consumed.semanticType) { // ← strict ===
...emit edge
}
}
}
createDeployment produces ProcessDefinitionKey, DecisionDefinitionKey, etc. getResourceContent consumes "ResourceKey". No producer string equals the consumer string, so zero edges are emitted for getResourceContent. Verified against the generated graph:
getResourceContent incoming edges: 0
ResourceKey in semanticTypes registry: false
- Total edges in graph: 32,966 (the equality model works fine for non-union types)
Planner behaviour
The planner correctly diagnoses the gap but emits the scenario anyway with a synthetic seed binding:
// generated/camunda-oca/scenarios/get--resources--{resourceKey}--content-scenarios.json
{
"id": "unsatisfied",
"missingSemanticTypes": ["ResourceKey"],
"seedBindings": ["resourceKeyVar"],
"operations": [{ "operationId": "getResourceContent", ... }]
}
The seed binding resourceKeyVar is then materialised by the generic free-form-string value provider as resourceKeyVar-<rand>, which fails both the LongKey regex (^-?[0-9]+$) and any lookup the server could perform.
Why a value-provider fix is not sufficient
Even a regex-compliant synthesised digit string (e.g. "2251799813686749") only changes the failure mode from 400 Bad Request (parse) to 404 Not Found (lookup). The operation still never executes against a real resource. The correct value can only come from a prereq step that produced one — i.e. the dependency chain has to exist.
Proposed fix
Two cooperating changes; either alone is insufficient.
-
Extractor — walk unions. Extend findSemanticTypeInSchema to traverse oneOf and anyOf in addition to allOf, returning the set of branch semantic types. Adjust extractParameter to record both the singular semanticType (for back-compat) and a new semanticTypeAlternatives: string[] field on OperationParameter. The ref-name heuristic at schema-analyzer.ts:365-372 should be removed (or kept only as a tertiary fallback when no branch alternatives could be derived); its current effect is to actively suppress the union resolution by short-circuiting with a synthetic name that doesn't exist in the registry.
-
Graph builder — accept branch matches. Relax the equality check to produced.semanticType === consumed.semanticType || consumed.semanticTypeAlternatives?.includes(produced.semanticType). Each branch producer becomes a valid edge for the union consumer.
Class-scoped regression guard
Layer-2 invariant that would have caught this and prevents recurrence:
For every operation parameter whose resolved schema (transitively through $ref, allOf, oneOf, anyOf) carries x-semantic-type on at least one branch, the operation must have at least one incoming edge in the dependency graph for at least one of those branch types — unless the schema is marked x-semantic-provider: true.
This currently fails for getResourceContent (and any other oneOf-of-branded-keys consumer). It would pass after fix #1 and fix #2 land.
Scope
Confirmed affected today:
getResourceContent, getResourceContentBinary, deleteResource, and any other operation on /v2/resources/{resourceKey}/....
- More broadly, every union-of-branded-keys parameter the spec exposes (path, query, or body).
Adjacent issues:
- The same pattern shows up in body-builder synthesis for
*Key array items — #326 populated arrays with 'placeholder' strings, which fails LongKey.pattern identically. The fix proposed here would also enable the body builder to synthesise real keys from producer chains for array-of-key fields, replacing the placeholder seed.
Reproduction
# from PR #329 branch, after pipeline regen + npm i in the playwright dir:
cd generated/camunda-oca/playwright
npx playwright test getResourceContent.feature.spec.ts
Observe 400 Bad Request — Failed to convert 'resourceKey' with value: 'resourceKeyVar-<rand>'.
Summary
Operations whose parameters are typed by a
oneOfunion of branded key schemas (e.g.ResourceKey) cannot be satisfied by the planner, even though producers exist for every branch. The extractor records the parameter with a synthetic semantic type derived from the ref name, no edges are emitted, the scenario is markedunsatisfied, and the value provider falls back to a non-compliant placeholder (<paramName>Var-<rand>). The server rejects with400 Bad Request — Failed to convert ....Concrete failing case from the live-cluster run on PR #329:
This is a class-of-defect, not an instance — every operation taking a
ResourceKey(or any futureoneOf-of-branded-keys parameter) is broken the same way. The two related root causes are summarised below.Spec shape
The four branches all have
x-semantic-typeand have producer operations (createDeploymentreturns each kind in its response items).ResourceKeyitself carries nox-semantic-type— by design, since it is a union.Root cause — extractor
semantic-graph-extractor/schema-analyzer.ts:767-783—findSemanticTypeInSchemaonly walksallOf, notoneOforanyOf:For the
resourceKeyparameter (schema: { $ref: '#/components/schemas/ResourceKey' }), this returnsundefined. The fallback heuristic atschema-analyzer.ts:365-372then kicks in:The parameter is recorded with
semanticType: "ResourceKey"purely from the ref name. But the registry (built byextractSemanticTypesFromSchemaatschema-analyzer.ts:61) only registers schemas that carry their ownx-semantic-type, soResourceKeyis never registered. The graph'ssemanticTypeslist has 41 entries (the four branch types are present) but noResourceKey.Root cause — graph-builder
semantic-graph-extractor/graph-builder.ts:60-83— edges are emitted by strict string equality:createDeploymentproducesProcessDefinitionKey,DecisionDefinitionKey, etc.getResourceContentconsumes"ResourceKey". No producer string equals the consumer string, so zero edges are emitted forgetResourceContent. Verified against the generated graph:getResourceContentincoming edges: 0ResourceKeyinsemanticTypesregistry: falsePlanner behaviour
The planner correctly diagnoses the gap but emits the scenario anyway with a synthetic seed binding:
The seed binding
resourceKeyVaris then materialised by the generic free-form-string value provider asresourceKeyVar-<rand>, which fails both theLongKeyregex (^-?[0-9]+$) and any lookup the server could perform.Why a value-provider fix is not sufficient
Even a regex-compliant synthesised digit string (e.g.
"2251799813686749") only changes the failure mode from400 Bad Request(parse) to404 Not Found(lookup). The operation still never executes against a real resource. The correct value can only come from a prereq step that produced one — i.e. the dependency chain has to exist.Proposed fix
Two cooperating changes; either alone is insufficient.
Extractor — walk unions. Extend
findSemanticTypeInSchemato traverseoneOfandanyOfin addition toallOf, returning the set of branch semantic types. AdjustextractParameterto record both the singularsemanticType(for back-compat) and a newsemanticTypeAlternatives: string[]field onOperationParameter. The ref-name heuristic atschema-analyzer.ts:365-372should be removed (or kept only as a tertiary fallback when no branch alternatives could be derived); its current effect is to actively suppress the union resolution by short-circuiting with a synthetic name that doesn't exist in the registry.Graph builder — accept branch matches. Relax the equality check to
produced.semanticType === consumed.semanticType || consumed.semanticTypeAlternatives?.includes(produced.semanticType). Each branch producer becomes a valid edge for the union consumer.Class-scoped regression guard
Layer-2 invariant that would have caught this and prevents recurrence:
This currently fails for
getResourceContent(and any otheroneOf-of-branded-keys consumer). It would pass after fix #1 and fix #2 land.Scope
Confirmed affected today:
getResourceContent,getResourceContentBinary,deleteResource, and any other operation on/v2/resources/{resourceKey}/....Adjacent issues:
*Keyarray items —#326populated arrays with'placeholder'strings, which failsLongKey.patternidentically. The fix proposed here would also enable the body builder to synthesise real keys from producer chains for array-of-key fields, replacing the placeholder seed.Reproduction
Observe
400 Bad Request — Failed to convert 'resourceKey' with value: 'resourceKeyVar-<rand>'.