Skip to content

extractor: oneOf-typed parameters (e.g. ResourceKey) are not resolved to producer chains, yielding non-compliant placeholders #330

@jwulf

Description

@jwulf

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-783findSemanticTypeInSchema 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.

  1. 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.

  2. 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>'.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions