Conversation
-requestPlan is used instead of scenario.operations -no placeholder stub output -Python literals for booleans and null
…ter (#354) - buildPythonUrlExpression/renderPythonValue: stop snake_casing the ctx lookup key so ctx.get() matches the ctx.set() key from scenario.bindings (previously always missed, silently falling back to broken literal defaults). - sdk-mapping.ts: model the real operation-map.json shape ({file,region,label}[]) instead of a fictitious {package,method,qualifiedName} shape, so resolvePythonMethodName's op-map lookup actually resolves SDK method names instead of always falling back to snake_case(operationId). - regression-invariants.test.ts: add L3 invariants asserting real emitted content (no 'pass # TODO: implement' stubs, every step comment has a matching client call), and exclude the intentional \ seed token from the unresolved-placeholder invariant (mirrors the JS SDK invariant's existing exception).
- render RANDOM through SeedBinding in generated C# - add the missing RestSdk.Models import - switch error assertions to HttpRequestException - keep PascalCase async operation names unchanged - move operation lookup to a Map-backed CsharpOperationMapSource
- suffix generated class names with the emit mode (e.g. Variant) so a feature suite and a variant suite for the same operationId no longer emit the same C# type name, which was a CS0101 duplicate-definition error once both files compiled into the same project - fix TestFixtureBase.cs template: interpolation holes in interpolated strings had escaped quotes around NextCounter args, which is invalid C# syntax inside a non-verbatim interpolated string; corrected to unescaped quotes - add a regression test asserting feature/variant suites emit distinct class names - fix the pre-existing HttpRequestException test, which asserted on a 200-status fixture that could never trigger the error-path branch
- csharp-sdk-emitter.test.ts: extract BASE_REQUEST_STEP so the error-path test doesn't spread the optional requestPlan[0] field (was 'Object is possibly undefined'). - csharp-sdk-mapping.test.ts: drop 'as const' on OPERATION_MAP so its readonly array literal type is assignable to the mutable CsharpOperationMap parameter type. Pre-existing on C#-sdk-emitter-fixes before this merge (confirmed by running tsc on that branch alone).
The real Camunda.Orchestration.Sdk NuGet package is a single flat namespace (client + request/response DTOs together); RestSdk.Models only exists in this repo's local vendored reference client and does not resolve against the published package. Confirmed via a real dotnet build against the restored 9.0.0 package (CS0234 + cascading CS0246s on every generated file).
The emitted pyproject.toml required camunda-orchestration-sdk>=10.0.0, but PyPI's highest stable release is 9.0.1 (10.x only exists as dev pre-releases pip excludes by default). pip install -e . always failed with 'No matching distribution found', on any OS. Confirmed real fix via a real pip install against the actual PyPI package.
Two fixes: 1. materialize-support.ts: add [tool.poetry] package-mode = false. The emitted project is a flat pytest suite with no importable package of its own; without this, poetry-core's build backend tries to build/install a camunda-sdk-tests package, finds no matching module/folder, and every install (pip install -e ., poetry install) fails. Confirmed via a real poetry install + poetry run pytest run. 2. emitter.ts: sanitize scenario names into valid Python identifiers for test function names, prefixed with the scenario's own id for uniqueness. Scenario names may contain spaces/dashes/# (e.g. 'createProcessInstance - bpmn #1'), which produced a SyntaxError on every single generated python-sdk file (100% uncollectable by pytest), and scenarios sharing a display name silently collided into one test function. Confirmed via a real pytest --collect-only run (486 tests now collect with zero errors, up from 0).
The published package is @camunda8/cli (npm view confirms @camunda8/c8ctl 404s); it installs the c8ctl binary. Discovered while walking through the Playwright test setup instructions.
The generated js-sdk test suites previously targeted a fabricated createApiClient/{status,data} contract that never matched the real @camunda8/sdk package, causing 601/601 test failures at runtime with TypeError: createApiClient is not a function.
Real contract (confirmed via live smoke tests against an installed @camunda8/sdk@8.8.13 and a running local broker):
- Client: new Camunda8().getOrchestrationClusterApiClientLoose(), zero-config via ZEEBE_REST_ADDRESS/CAMUNDA_AUTH_STRATEGY env vars.
- Success responses are raw data, not wrapped in {data,...}; no .status on success.
- Errors are thrown Error-like objects with .status.
- ~60-92 eventually-consistent get*/search* methods require a second { consistency: { waitUpToMs } } argument or throw a client-side error; detected via runtime arity (method.length >= 2) rather than a hardcoded operation list.
- Detached method references must be .bind(client)'d before invoking (the SDK's methods read this._client internally).
- Method-name resolution now uses a generic toSdkMethodName() (acronym normalization + lowercase-first-char) instead of the unreliable operation-map.json (whose region values are doc-example identifiers, not real method names, and are ambiguous for oneOf variants).
Also rewrote the emitted package.json dependency version, .env.example, ambient camunda8-sdk.d.ts shim, and README to match the real contract.
Validated end-to-end against a live local broker: getTopology and searchUsers.feature suites pass for real. Known out-of-scope residual gap: some operationIds (e.g. getProcessInstanceStatisticsByError/ByDefinition) collapse to a single real SDK method and are not yet aliased -- flagged as follow-up, same class as the existing C# DTO-naming gap.
Audited all 202 operationIds against the real, installed @camunda8/sdk client (Object.getOwnPropertyNames(Object.getPrototypeOf(client))): - Zero true N:1 operationId-to-method name collisions exist. A prior assumption that 3 operationIds collapsed onto a single method (getProcessInstanceStatistics) was wrong -- each keeps its own distinct computed name, which simply has no backing method. - 56 of 202 operationIds (28%) have no corresponding method at all -- not a naming mismatch, but whole feature areas missing from the installed SDK (Agent Instance, Cluster Variables, Global Task Listener, most *Statistics reads, several process-instance lifecycle + batch-operation ops, etc). Root cause: spec/SDK version skew -- configs/camunda-oca/spec-pin.json tracks camunda/camunda@main, while @camunda8/sdk's published releases lag behind newer API areas. Confirmed not fixable via a version bump in this repo: the underlying @camunda8/orchestration-cluster-api@9.1.4 already resolves 183/202, but @camunda8/sdk (including its alpha channel) pins that dependency to \>=8.8.4 <9.0.0\, excluding 9.x entirely. Since no code change here can call a method that doesn't exist, these operations previously threw an opaque \TypeError: Cannot read properties of undefined (reading 'length')\ at the consistency-arg check. This adds generation-time detection instead: - materializer/scripts/dump-js-sdk-methods.mjs dumps real client method names from the installed @camunda8/sdk into a checked-in materializer/src/js-sdk/known-sdk-methods.json (regenerate via \ pm run js-sdk:dump-methods --workspace materializer\ whenever the @camunda8/sdk devDependency bumps). - emitter.ts's renderScenarioTest checks every step's resolved SDK method name against this list; if any step in a scenario has no backing method, the whole scenario emits as \it.skip(...)\ with a comment naming the missing method(s), instead of runnable code that crashes. Also fixes a stale API_BASE_URL reference in the generated README's troubleshooting section (leftover from the old fabricated SDK contract) and documents the new skip behavior there.
…e alias a95b227 introduced an inline \�s (...args: unknown[]) => Promise<any>\ cast on every rendered request step. biome.generated.json escalates noExplicitAny to error for generated/**/*.ts, and js-sdk is an enabled emitter for camunda-oca -- so \ pm run biome:fix-generated:codegen\ (and CI's lint:generated gate) failed with 800+ errors across the regenerated suite. A per-call \// biome-ignore\ comment doesn't reliably suppress this: the formatter wraps long inline casts across multiple lines, which can separate the ignore comment (attached to the line above the statement) from the specific line the any token lands on after wrapping. Instead, declare a single \ ype SdkCall = (...args: unknown[]) => Promise<any>;\ once per test, with one stable, always-short, ignored line -- every per-step call site then reads \�s SdkCall\ with no any token to trip the rule regardless of reformatting.
…hem undefined Ports the Playwright emitter's canonical seed-binding mechanism (emitCtxSeeding/computeUniqueBindings, reused directly rather than reimplemented) into the js-sdk emitter. Previously a scenario binding with no in-scenario producer step (bindings[x] === '__PENDING__') was emitted as ctx['x'] = undefined, silently dropping required request-body fields and causing widespread HttpSdkError: INVALID_ARGUMENT failures against a real broker (932 occurrences, ~291/337 runnable tests failing). Now uses the already-planner-computed scenario.seedBindings to emit a deterministic seedBinding() call, vendored into a new materializer/src/js-sdk/materialize-support.ts support/seeding.ts scaffolding file (js-sdk suites are nested one level deeper than Playwright's, so the import path is '../support/seeding'). Adds a regression fixture to tests/codegen/js-sdk-emitter.test.ts.
…he dead pathParams field
- Replace mechanical PascalCase(operationId)+Request DTO naming with explicit lookup table (CSHARP_REQUEST_TYPE_BY_OPERATION) that maps to real published SDK type names - Add resolveRequestTypeName() helper to emitter.ts - Replace dead step.pathParams fallback with derivePathParamNames() that extracts path params from pathTemplate via regex, matching the approach already used in the js-sdk emitter - Add toCamelCase() helper for path param variable name generation - Fix readonly-array typing error in csharp-sdk-mapping.test.ts by restoring explicit CsharpOperationMap type annotation
…reverted by 4d35987 - Restore CSHARP_REQUEST_TYPE_BY_OPERATION lookup table - Restore resolveRequestTypeName() helper - Restore derivePathParamNames() and toCamelCase() helpers - Restore PATH_PARAM_RE regex constant - Restore both regression tests that were deleted by the revert - Re-adds throw for unmapped operations instead of silently emitting wrong DTO name
- Replace sparse local 9-entry map with complete v9 published SDK map sourced from camunda/orchestration-cluster-api-csharp stable/9 at commit d559644 - All 183 mapped regions resolve to real CamundaClient methods - Convert upstream region labels to actual async method names - 3 spec operations confirmed to have no SDK implementation: getAgentInstance, searchAgentInstances, searchResources
js-sdk hardcoded globalContextSeeds: [], so the omitWhenUnbound tenant mechanism (already used by playwright + csharp-sdk) never applied. Every scenario minted a random tenantIdVar, which a single-tenant broker rejects with INVALID_ARGUMENT on createDeployment and every op downstream of it. Threads ctx.globalContextSeeds through renderJsSuite / computeScenarioSeedLines, with the same assertSafeGlobalContextSeeds boundary check the C# emitter uses. Verified against a live broker: js-sdk went from 52 passed/289 failed to 156 passed/185 failed (671 total).
…pIterationId, AgentHistoryItemKey) Upstream added AI Agent Connector request-body fields whose ID-like values were unclassified in the semantics ABox, so the planner's DomainSemanticsValidationFailure aborted codegen before any suite (playwright/js-sdk/python-sdk/csharp-sdk) could be emitted. All three are client-minted filter/correlation attributes, not server-emitted lifecycle keys, matching the existing Tag/BusinessId pattern. Verified: npm run extract-graph completes with no unclassified semantic types against the current bundled spec.
… (9.2.2) Version="1.0.0" does not exist on nuget.org for Camunda.Orchestration.Sdk (published versions start at 8.9.0-alpha.2; 9.0.0 is the lowest stable release), so dotnet restore could never resolve it from a clean cache. 9.2.2 is the latest stable 9.x release, matching the python-sdk emitter's >=9.0.0 convention. Note: this unblocks restore but does not fix compilation — the emitted call sites still don't match the real SDK's method signatures (missing required search-query/request-body arguments, object-vs-string? mismatches in RequireBinding call sites). That's a separate emitter bug.
Split out of PR #573 (combined/sdk-emitter-fixes) into a standalone per-SDK PR. - Strongly-typed key-struct construction: CSHARP_PATH_PARAM_KEY_TYPE map + `.AssumeExists(...)` codegen for path params, falling back to a plain string binding for unmapped names (not every path param is a key struct, e.g. getUser's username). - StringValueObjectConverterFactory (TestFixtureBase.cs): reflection- detects any SDK key-struct type (Value property + AssumeExists/ IsValid) and (de)serializes it as a plain JSON string, fixing both request-body round-tripping and response field-path extraction. - ExtendedDeploymentResponse-style "Raw" wrapper unwrapping in ToJsonElement, so field-path extraction matches the real wire shape instead of the C#-ergonomic PascalCase mirror properties. - Fixture path resolution reuses TestFixtureBase's ResolveFixturePath() (was building an inline, less-robust path assuming fixtures/ sits next to the test binary). - Missing operationId -> request-DTO mappings (createDocumentLink, deleteResource, resolveIncident, modifyProcessInstance, several *Statistics reads, etc.) and corrected 3 pre-existing wrong mappings. - CSHARP_VOID_METHODS: bare-Task-returning SDK methods (GetStatusAsync, ResetClockAsync, ~48 Assign*/Unassign*/Delete* RBAC methods, etc.) no longer get an implicitly-typed `var result =` assignment (CS0815). - JSON body rendering gained omitWhenUnbound awareness (previously only the multipart-fields loop had it) via partitionJsonFields/collectJsonRequestFields/emitJsonRequestDataLines. - renderTenantExpr uses GetStringBindingOrNull (deployment tenantId is always broker-optional) instead of a throwing RequireStringBinding. - eventualWaitsAfter witness-polling: AwaitEventuallyWitness + WitnessPredicateMatches (TestFixtureBase.cs) plus emitter wiring at all 3 success-path exits, mirroring the js-sdk emitter's pattern. - Repo housekeeping swept in from the original combined branch's history (unrelated to any single SDK, folded in here rather than a separate PR): hashicorp/vault-action v3->v4 + create-github-app-token v2->v3 bumps, .dockerignore, README's @camunda8/c8ctl -> @camunda8/cli package-name fix, spec-pin bump, and a run-hub.sh fix so a generate-only STEPS run no longer requires a live Hub/Keycloak.
…response-shape assertion, auth header, invariant precision; js-sdk dist JSON staging, witness consistency argument)
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical and moderate generation, runtime, rendering, E2E, and regression-test issues remain.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (12)
materializer/src/csharp-sdk/emitter.ts:23
- The request-type table is still incomplete for body-bearing operations that are present in the C# operation map. For example, the planner emits
updateUserTask(and the same applies tocreateAuthorization/createRoleand the CRUD update operations), but none has an entry here;resolveRequestTypeName()then returnsundefinedand the non-empty body path throwsNo published C# request DTO mapping foundduring generation. Add the verified DTO mappings for every body operation before generating the full C# suite.
const CSHARP_REQUEST_TYPE_BY_OPERATION: Record<string, string> = {
createDeployment: 'DeploymentRequest',
createUser: 'UserRequest',
createTenant: 'TenantCreateRequest',
createGroup: 'GroupCreateRequest',
createMappingRule: 'MappingRuleCreateRequest',
materializer/src/js-sdk/emitter.ts:181
initSpecSalt()is still emitted at module scope, but every generated operation file imports the samesupport/seedingmodule whose_specSaltis global. Vitest loads multiple files before running their tests, so the last module imported overwrites the salt for all earlier suites; seed values then depend on import order and lose the per-operation isolation this helper is meant to provide. Emit the initialization inside each generateditbody immediately before that scenario's seed calls, as the Python emitter now does.
if (needsSeeding) {
// Mixed into the seed so parallel vitest workers (sharing one TEST_SEED)
// don't draw the same sequence for the same binding name.
lines.push(`initSpecSalt(${JSON.stringify(operationId)});`);
lines.push('');
materializer/src/js-sdk/emitter.ts:400
RequestStep.extract.fieldPathis emitted from the OpenAPI response fields (for example,id), but the SDK call returns an envelope whose payload is underresponse.data. This assignment therefore readsresponse.idfor generated plans, leaving prerequisite bindings undefined and breaking later path/body inputs. Normalize toresponse.databefore applying the planner accessor, and only assign when the extracted value is notundefinedso an absent field cannot erase an earlier binding.
lines.push(` ctx['${extract.bind}'] = ${responseVar}${accessor};`);
materializer/src/js-sdk/emitter.ts:452
- The witness predicate is applied to the raw SDK response wrapper, whose REST payload is under
.data, whileWitnessPredicate.pathis the top-level wire-body field (for example,state). As written, the predicate readsresponse.stateinstead ofresponse.data.state, so eventual waits retry until timeout even after the witness reaches the expected state.
` const v = (body as Record<string, unknown>)[${JSON.stringify(w.predicate.path)}];`,
materializer/src/js-sdk/sdk-mapping.ts:24
- Falsy but valid JSON body templates (
false,0,'', ornull) are rendered as{}. Since the request plan permits arbitrary JSON values, this silently changes scalar payloads; use anundefinedcheck for absence and preserve the literal value (and update the caller's truthiness check accordingly).
if (!bodyTemplate) return '{}';
materializer/src/js-sdk/sdk-mapping.ts:32
- Only whole-string
${...}values are converted to context lookups here, while the planner recognizes embedded and multiple placeholders in a string. A body value likeprefix-${nameVar}-${idVar}therefore remains a literal in generated JavaScript even though both bindings are collected and seeded, producing an incorrect request.
const bindingMatch = value.match(/^\\?\$\{([^}]+)\}$/);
if (bindingMatch) return `ctx['${bindingMatch[1]}']`;
return JSON.stringify(value);
materializer/src/python-sdk/emitter.ts:142
- The Python emitter now keeps planner binding names in their original camelCase (
ctx.get('tenantIdVar')), but this doc block still says placeholders becomectx.get('snake_var')and the example above usesid_var. That contradicts the generated code and the regression assertions, so contributors following the documentation can reintroduce the casing bug; update the example/docstring to useidVar/tenantIdVar(or change the implementation consistently).
// ctx keys are the planner's original binding variable names (e.g.
// tenantIdVar) — must match the ctx.set(...) calls emitted for
// scenario.bindings verbatim, so no casing transform here (#354).
return `ctx.get('${whole[1]}')`;
materializer/src/python-sdk/emitter.ts:138
- This matcher only substitutes a placeholder when it occupies the entire string. The planner collects embedded and multiple
${...}references, so values such asprefix-${nameVar}-${idVar}are seeded but emitted literally and reach the broker unresolved. Render all placeholder matches as a concatenation of literals and context values instead of limiting substitution to whole-string values.
const whole = /^\$\{([^}]+)\}$/.exec(value);
if (whole) {
materializer/src/python-sdk/emitter.ts:556
- This unconditionally stores
get_nested_value(...)in the context. The helper returnsNoneboth for a missing response field and for a JSON null, so a missing optional field can erase a previously seeded or extracted binding and make a later request send null. Match the other emitters' extraction contract by distinguishing a missing path and leaving the existing binding untouched.
` ctx.set('${extract.bind}', get_nested_value(${responseDataVar}, '${extract.fieldPath}'))`,
materializer/src/python-sdk/emitter.ts:165
- The
renderPythonValuecall is preceded by a falsy check that treats valid scalar JSON bodies (false,0,'', andnull) as if no body existed, so this path emits{}instead. UsebodyTemplate === undefinedas the no-body check and preserve all other JSON values.
return renderPythonValue(bodyTemplate);
scripts/e2e/run-hub.sh:66
- By adding
curlto this live-step condition, the script now supports curl-only execution, but the resource-fixture block below still runs only whenstep runis selected.STEPS=curltherefore reachescurl_compare.pywithout the valid keys its negative requests need and can report 404/403 instead of the expected validation responses. The fixture setup must also run for curl-only mode.
tests/regression/generated-suites-typecheck.test.ts:59 - The PR changes the Python and C# emitters and their generated-project scaffolding, but this regression harness adds a typecheck only for
js-sdk. A generated Python syntax/import or C# compile regression can therefore pass the repository test suite even though these are the real-toolchain paths this PR is fixing; add equivalent generated-output checks for both targets (or a separate CI-backed harness).
- Files reviewed: 35/37 changed files
- Comments generated: 2
- Review effort level: Lite
…mitter) - renderPythonValue: mixed literal+\ strings are now rendered as Python f-strings so embedded bindings are no longer silently dropped when a placeholder doesn't occupy the entire string. - Scenario materialization now throws at generation time for an empty requestPlan instead of emitting a comment-only test body that pytest reports as a silent pass. - Added dedicated test coverage for renderPythonEventualWait's witness URL binding, predicate match, timeout, and poll-interval branches.
… js-sdk emitter (Copilot PR #575 review, round 2)
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical JavaScript response-path issues and additional seed-binding and C# mapping defects block approval.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (4)
Previously missed (1) — in code that hasn't changed since the last review.
materializer/src/js-sdk/sdk-mapping.ts:32
seedBindings.tsdeliberately recognizes placeholders embedded anywhere in a string and supports multiple placeholders, but this renderer only substitutes a whole-string match. A template such asproc-${processInstanceKeyVar}-${tenantIdVar}is emitted with the literal placeholder text, causing the generated request to send unresolved bindings instead of their seeded values; use an embedded replacement path here and add the same regression coverage as the Python emitter.
materializer/src/csharp-sdk/emitter.ts:104
- This key-type map is incomplete for the pinned 9.2.2 SDK: methods using
{tenantId}takeTenantId, and methods using{username}takeUsername, but both names fall through toRequireStringBindingbelow. Generated calls such as tenant membership operations then pass strings to strongly typed SDK parameters and fail to compile; add these value-object mappings (and audit the remaining SDK signatures) instead of treating every unmapped path parameter as a string.
processInstanceKey: 'ProcessInstanceKey',
resourceKey: 'ResourceKey',
userTaskKey: 'UserTaskKey',
variableKey: 'VariableKey',
};
materializer/src/js-sdk/emitter.ts:61
emitCtxSeedingwrites literal scenario bindings verbatim, so planner values such asproc_${RANDOM}are generated into the JS suite as a quoted literal instead of a runtime-generated identifier. The scenario generator marks these as placeholders consumed by the test runtime; route theRANDOMtoken through the generated seeding helper (and add a regression case) rather than passing it unchanged.
bindings: scenario.bindings,
seedBindings: scenario.seedBindings,
globalContextSeeds,
materializer/src/python-sdk/emitter.ts:159
scenarioGeneratoremits literal bindings such asproc_${RANDOM}andjobType_${RANDOM}(path-analyser/src/scenarioGenerator.ts:443-449). This interpolation treatsRANDOMas a missing context key and falls back to an empty string, producingproc_/jobType_; these literals should be expanded throughseed_binding('RANDOM')(with the corresponding import) just as the C# emitter handlesSeedBinding("RANDOM").
for (const match of value.matchAll(EMBEDDED_PLACEHOLDER_RE)) {
rendered += escapeLiteral(value.slice(lastIndex, match.index));
rendered += `{ctx.get('${match[1]}') or ''}`;
lastIndex = match.index + match[0].length;
- Files reviewed: 35/37 changed files
- Comments generated: 2
- Review effort level: Lite
| for (const extract of step.extract) { | ||
| const accessor = toOptionalAccessor(extract.fieldPath); | ||
| lines.push(` ctx['${extract.bind}'] = ${responseVar}.data${accessor};`); | ||
| lines.push(` ctx['${extract.bind}'] = ${responseVar}${accessor};`); |
| lines.push( | ||
| ` const v = (body as Record<string, unknown>)[${JSON.stringify(w.predicate.path)}];`, | ||
| ); | ||
| lines.push(` return v === ${JSON.stringify(w.predicate.equals)};`); |
…lbacks in python-sdk emitter (Copilot PR #574 review, round 3)
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical and moderate findings remain in token and placeholder rendering, generated-output validation, and specification-pin justification.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (6)
configs/camunda-oca/spec-pin.json:4
- This changes the camunda-oca baseline to a different upstream spec commit and content hash, but the PR description presents only SDK-emitter fixes and does not document a spec bump or the resulting invariant/output changes. Because this changes the contract that all generated scenarios are validated against, please remove it from this emitter PR or explicitly justify the pin bump and include the required regenerated/invariant review.
"specRef": "6ea2724e1e03f26508d07748b66a7ca54e9c595b",
"expectedSpecHash": "sha256:798dfd63863ec3bfa266ccc46e0695082ec9aa1ed45923bd9fb0cb130d4f85db"
materializer/src/js-sdk/emitter.ts:406
- When the SDK call unexpectedly succeeds,
expect.fail(...)throws inside thistryand is immediately caught as if it were anHttpSdkError; the test then reportssdkError.statusasundefinedinstead of the intended “expected N but request succeeded” failure. Move the explicit failure outside thetry/catch(or return after handling the SDK error) so unexpected success is not misclassified.
if (isErrorExpected) {
lines.push(' try {');
lines.push(
` const ${responseVar} = await call${stepNum}(input${stepNum}, consistency${stepNum});`,
);
lines.push(` expect.fail('Expected ${expectedStatus} but request succeeded');`);
lines.push(' } catch (error) {');
lines.push(' const sdkError = error as HttpSdkError;');
lines.push(` expect(sdkError.status).toBe(${expectedStatus});`);
lines.push(' }');
materializer/src/python-sdk/emitter.ts:164
- The embedded-placeholder branch has the same
${RANDOM}problem:proc_${RANDOM}becomes an f-string expression reading an absentRANDOMcontext key and therefore loses the process/job type suffix. Preserve this token as escaped literal f-string text rather than evaluating it as a binding.
for (const match of value.matchAll(EMBEDDED_PLACEHOLDER_RE)) {
rendered += escapeLiteral(value.slice(lastIndex, match.index));
// `is not None`, not `or`: a falsy-but-bound value like 0 or False must
// not collapse to '' (Copilot PR #574 review, same class as the
// path-param fix in buildPythonUrlExpression above).
rendered += `{ctx.get('${match[1]}') if ctx.get('${match[1]}') is not None else ''}`;
scripts/e2e/run-hub.sh:72
- This new comment is grammatically incomplete and leaves the inline code span unterminated, so it does not clearly document what header is intentionally omitted.
tests/regression/generated-suites-typecheck.test.ts:59 - This test now assumes every active config emits a JS SDK suite, but
configs/camunda-hub/codegen/emitters.json:1-3enables only Playwright. WithCONFIG=camunda-hub,getSdkOutDir(..., 'js-sdk')/tsconfig.jsonis absent and this test throws before checking any suite. Gate this entry on the active config's enabled-emitter registry, consistent with the config-driven target layout.
tests/regression/generated-suites-typecheck.test.ts:59 - This new generated-suite guard covers only TypeScript. The PR also changes Python request rendering and several C# DTO/path/return-type mappings, where emitter string tests can pass while the emitted project still fails
poetry/dotnetcompilation. Add equivalent generated Python syntax/type and C# build checks (or an explicitly scoped CI job) so those real-toolchain regressions are guarded rather than validated only manually.
- Files reviewed: 35/37 changed files
- Comments generated: 3
- Review effort level: Lite
| function renderTemplateToken(name: string): string { | ||
| if (name === 'RANDOM') { | ||
| return 'SeedBinding("RANDOM")'; | ||
| } |
…d-template rendering (Copilot PR #574 review, round 4) - renderPythonTemplateString now special-cases the '${RANDOM}' token (planner-minted literal seed value, e.g. 'proc_${RANDOM}') as literal text instead of resolving it via ctx.get('RANDOM'), for both the whole-string and embedded-mixed-string cases -- mirrors how the JS/Playwright emitters never resolve this token either. - Switched mixed-string rendering from an f-string to '+' concatenation of string-literal and ctx-lookup segments: an f-string's brace-doubling escaping would have corrupted the literal token, tripping regression-invariants.test.ts's exact-substring check (#133). - Replaced the stale SAMPLE_COLLECTION-based whole-string-placeholder test (which asserted a negative regex against a fixture with no actual placeholder, so it could never fail) with a real widgetIdVar fixture asserting the actual ctx.get(...) lookup. - Added regression tests for both the whole-string and embedded RANDOM cases.
- Bump documented/enforced minimum Node version to >=18.13.0 (engines
field in scaffolded package.json + README), matching the `File`
global's real availability on `node:buffer`.
- Sanitize line-break characters (\r, \n, U+2028, U+2029) out of the
missing-method skip reason before embedding it in the generated
`// SKIPPED: ...` line comment, closing a comment-injection risk from
a hostile/malformed operationId.
- renderJavaScriptBody(): support mixed literal/placeholder strings
(e.g. 'proc-${a}-${b}') by rendering a real JS template literal
instead of falling through to an unresolved JSON.stringify() literal,
mirroring the python-sdk emitter's equivalent fix. Also fixes a
latent whole-string bug where the planner's literal `${RANDOM}` seed
token was incorrectly resolved to `ctx['RANDOM']`.
- Gate the `js-sdk` entry in generated-suites-typecheck.test.ts's SUITES
matrix on the active config's codegen/emitters.json actually
declaring js-sdk, so camunda-hub (playwright-only) doesn't fail with
a missing-tsconfig error.
- Confirmed (no code change needed): the require()-based
known-sdk-methods.json dist-staging concern flagged against
emitter.ts/materialize-support.ts is already resolved by
materializer/scripts/copy-support-templates.js, and the File-global
sdk-mapping.ts:74 concern is already resolved by emitter.ts's
conditional `import { File } from 'node:buffer'` (Round 2).
New/updated tests in tests/codegen/js-sdk-emitter.test.ts and
tests/regression/generated-suites-typecheck.test.ts.
… fallback too (Copilot PR #574 review, round 4) hasMultipartStep previously only checked step.multipartTemplate !== undefined, missing the bodyTemplate fallback path (payloadTemplate = multipartTemplate ?? bodyTemplate) that renderPythonMultipartFiles also resolves @@file: markers from. A multipart step relying on that fallback still called resolve_fixture(...) without the import being emitted, producing a NameError at runtime. Added containsFixtureMarker() to recursively detect @@file: markers in either multipartTemplate or bodyTemplate and gate the import on that instead.
…bjectConverter<T> (Copilot PR #576 review) StringValueObjectConverter<T> is a private nested class; Activator.CreateInstance(Type) only probes public constructors by default. Pass nonPublic: true so the converter factory reliably works regardless of compiler/runtime-specific implicit-constructor accessibility behavior.
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical and moderate findings remain around generated literal escaping, falsy request bodies, fixture resolution, and exact status validation.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (10)
Previously missed (1) — in code that hasn't changed since the last review.
materializer/src/python-sdk/emitter.ts:228
- The scalar JSON branch calls
renderPythonBodyforfalse,0, and'', but the preceding truthiness check converts all of them to{}.RequestStep.bodyTemplateisunknownand the C# emitter already handles scalar bodies, so this drops the documented request payload for valid primitive schemas; only an absent (undefined) template should take the no-body path.
* Substitutes placeholders like "${varName}" with ctx.get('var_name').
materializer/src/csharp-sdk/emitter.ts:564
- The new bare-
Taskbranch treats any non-throwing completion as success, but these SDK methods only enforce the broad 2xx range and expose no response status. Consequently a step expecting one exact status (for example 204) cannot detect a different successful status. Use a status-returning SDK path for these operations or explicitly model that exact status is unavailable instead of silently passing.
// to compile with CS0815. A successful (non-throwing) completion is the
// pass condition for these calls, so there is nothing to assert or
// extract from.
materializer/src/csharp-sdk/emitter.ts:601
- This generated assertion claims to check the planner's exact status, but the installed SDK methods return DTOs after
EnsureSuccessStatusCode()and do not expose the HTTP response.AssertExpectedStatustherefore falls back to accepting any 2xx, so an expected 201/204 can pass after an unexpected 200/202. The call must use a status-aware SDK response (or the exact-status limitation must be modeled explicitly).
body.push(` AssertExpectedStatus(${varName}, ${step.expect.status});`);
materializer/src/js-sdk/emitter.ts:415
- The successful branch forwards the request but never checks
step.expect.status. Because the loose SDK client only throws for non-2xx responses, an expected 201/204 can silently pass when the server returns a different 2xx status. Emit an exact-status assertion using a status-aware SDK response before recording extracts.
` const ${responseVar} = await call${stepNum}(input${stepNum}, consistency${stepNum});`,
materializer/src/js-sdk/sdk-mapping.ts:24
- This helper is also used for each multipart field value, so a valid falsy scalar such as
falseor0is rendered as{}instead of the scalar. Because request templates are typed asunknownand the sibling emitters support scalar values, use an undefined-only no-body check here (and preservenullasnull) so multipart fields and scalar JSON bodies retain their wire type.
if (!bodyTemplate) return '{}';
materializer/src/python-sdk/emitter.ts:186
- The newly added embedded-placeholder branch interpolates
varNamedirectly into a single-quoted Python string literal. A spec-derived binding containing',\, or a newline makes the generated test invalid or allows source injection; render the key withrenderPythonStringLiteral(varName)in bothctx.get(...)calls.
parts.push(renderPythonStringLiteral(literalBuffer));
materializer/src/python-sdk/emitter.ts:255
fieldNameis another spec-derived value inserted into a single-quoted Python literal without escaping. The new dictionary-assignment path will emit invalid source for a property such asowner'sName; userenderPythonStringLiteral(fieldName)here, consistently with the multipart fix.
const deferred: { fieldName: string; binding: string }[] = [];
materializer/src/python-sdk/emitter.ts:262
- This deferred branch has the same code-generation bug as the inline branch:
fieldNameis interpolated into a Python string literal without escaping. When an omit-when-unbound field has a non-identifier name, the emitted suite can fail to parse or assign the wrong key.
inlineEntries.push(`'${fieldName}': ${renderPythonValue(fieldValue)}`);
materializer/src/python-sdk/materialize-support.ts:251
- The generated project vendors its fixtures specifically so resolution is independent of
cwd, but these two candidates search the caller's working directory before the vendored/config fixture roots. Running pytest from the repository (or another directory containing the same relative name) can therefore silently load an unrelated file instead of the fixture selected by the generator. Remove the direct relative-path lookups and resolve only from the generated/configured fixture roots.
candidates = [
Path(relative_path),
Path.cwd() / relative_path,
Path.cwd() / 'fixtures' / relative_path,
tests/codegen/js-sdk-emitter.test.ts:319
- This assertion locks in a missing status contract: the planner supplies
expect.status, but a successful loose-client call can return 200 for an expected 201/204 without throwing. Generated JS suites therefore accept the wrong successful status and continue to extract data. Remove the negative assertion and make the generated success path verify the exact status (or use an SDK API that exposes it).
- Files reviewed: 35/37 changed files
- Comments generated: 3
- Review effort level: Lite
| // biome-ignore lint/suspicious/noTemplateCurlyInString: literal escaped `${RANDOM}` seed token text to embed verbatim in the emitted template literal | ||
| parts.push('\\${RANDOM}'); | ||
| } else { | ||
| parts.push(`\${ctx['${match[1]}']}`); |
| // ctx keys are the planner's original binding variable names (e.g. | ||
| // tenantIdVar) — must match the ctx.set(...) calls emitted for | ||
| // scenario.bindings verbatim, so no casing transform here (#354). | ||
| return `ctx.get('${whole[1]}')`; |
| if (typeof value === 'string' && value.startsWith('@@FILE:')) { | ||
| const fixturePath = value.slice('@@FILE:'.length); | ||
| const filename = fixturePath.split('/').pop() || key; | ||
| return `'${key}': (${renderPythonStringLiteral(filename)}, resolve_fixture(${renderPythonStringLiteral(fixturePath)}))`; |
There was a problem hiding this comment.
🟡 Changes recommended
One critical fixture-path safety issue and three moderate request-handling issues remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (2)
materializer/src/js-sdk/emitter.ts:473
- The loose client returns a response wrapper whose payload is under
data(the emitter already emitsresponse?.datafor extraction), but this new witness predicate readsstate/other planner paths directly from the wrapper. Every eventual wait therefore evaluatesundefined === expectedand times out even after the API returns the desired payload. Apply the predicate tobody.data(or unwrap the response before passing it toawaitEventually).
` const v = (body as Record<string, unknown>)[${JSON.stringify(w.predicate.path)}];`,
materializer/src/python-sdk/emitter.ts:235
- The body renderer's falsy guard above treats every falsy template as absent. Since this path now intentionally renders any non-
undefinedrequest body, valid top-level JSON bodies such asfalse,0,'', ornullreach this return but are emitted as{}instead of their planned value. Change the guard to anundefinedcheck and letrenderPythonValuehandle scalar/null bodies.
return renderPythonValue(bodyTemplate);
- Files reviewed: 35/37 changed files
- Comments generated: 1
- Review effort level: Lite
| candidates = [ | ||
| Path(relative_path), | ||
| Path.cwd() / relative_path, | ||
| Path.cwd() / 'fixtures' / relative_path, | ||
| Path.cwd() / 'configs' / active_config / 'fixtures' / relative_path, | ||
| here.parent / 'fixtures' / relative_path, | ||
| here.parent.parent / 'fixtures' / relative_path, | ||
| here.parent.parent.parent / 'fixtures' / relative_path, | ||
| ] |
* fix: real-toolchain-validated python-sdk emitter fixes Split out of PR #573 (combined/sdk-emitter-fixes) into a standalone per-SDK PR. - Real httpx-based Python SDK request execution (no longer a self-mocked AsyncMock suite): multipart @@file fixture resolution, seed-binding restoration, f-string quote-nesting fix, path-param derivation off the raw path template. - materializePythonFixtures: vendors BPMN/DMN/form fixtures into the generated python-sdk output dir. - omitWhenUnbound/unique-binding awareness in the seedBindings loop (reuses playwright/ctxSeeding.ts, no reimplementation). - init_spec_salt(...) moved to per-test placement (was module-scope, causing cross-module salt collisions under pytest's import-all collection). - get_nested_value bracket-notation field paths; OperationMapSource has()/lookup() consistency fix. - New/updated regression invariants for the emitted Python SDK suite (#133, #354): no leftover pass # TODO stubs, every Step N: comment has a matching real client call, runtime seed tokens are not misflagged as missing bindings. * fix: address Copilot PR #574 review comments (response-shape assertion, invariant precision, auth header) * fix: address round-2 Copilot review comments on PR #574 (python-sdk emitter) - renderPythonValue: mixed literal+\ strings are now rendered as Python f-strings so embedded bindings are no longer silently dropped when a placeholder doesn't occupy the entire string. - Scenario materialization now throws at generation time for an empty requestPlan instead of emitting a comment-only test body that pytest reports as a silent pass. - Added dedicated test coverage for renderPythonEventualWait's witness URL binding, predicate match, timeout, and poll-interval branches. * fix: use is-not-None checks instead of or for path-param/template fallbacks in python-sdk emitter (Copilot PR #574 review, round 3) * fix: preserve literal ${RANDOM} runtime seed token in python-sdk mixed-template rendering (Copilot PR #574 review, round 4) - renderPythonTemplateString now special-cases the '${RANDOM}' token (planner-minted literal seed value, e.g. 'proc_${RANDOM}') as literal text instead of resolving it via ctx.get('RANDOM'), for both the whole-string and embedded-mixed-string cases -- mirrors how the JS/Playwright emitters never resolve this token either. - Switched mixed-string rendering from an f-string to '+' concatenation of string-literal and ctx-lookup segments: an f-string's brace-doubling escaping would have corrupted the literal token, tripping regression-invariants.test.ts's exact-substring check (#133). - Replaced the stale SAMPLE_COLLECTION-based whole-string-placeholder test (which asserted a negative regex against a fixture with no actual placeholder, so it could never fail) with a real widgetIdVar fixture asserting the actual ctx.get(...) lookup. - Added regression tests for both the whole-string and embedded RANDOM cases. * fix: gate python-sdk resolve_fixture import on multipart bodyTemplate fallback too (Copilot PR #574 review, round 4) hasMultipartStep previously only checked step.multipartTemplate !== undefined, missing the bodyTemplate fallback path (payloadTemplate = multipartTemplate ?? bodyTemplate) that renderPythonMultipartFiles also resolves @@file: markers from. A multipart step relying on that fallback still called resolve_fixture(...) without the import being emitted, producing a NameError at runtime. Added containsFixtureMarker() to recursively detect @@file: markers in either multipartTemplate or bodyTemplate and gate the import on that instead. * fix: harden python-sdk emitter body-verbs, extract-vs-null, 429 retry, fixture order + docs Addresses the 9 suppressed/low-confidence advisories from PR #574 review round 4: - emitter.ts: use client.request(<METHOD>, ...) instead of the verb-specific convenience method when a get/delete/options/head step carries a json=/data=/files= body, since httpx.AsyncClient's convenience methods reject those kwargs. - emitter.ts: get_nested_value now returns a _MISSING sentinel (not None) so a missing/absent field no longer overwrites an existing seed or earlier extract via ctx.set; an explicit JSON null is still set as a real value. - emitter.ts: the eventual-wait polling loop now retries on a 429 response (like 404) instead of treating it as terminal, so a transient broker rate-limit doesn't fail the wait early. - materialize-support.ts: resolve_fixture now prefers the vendored <suite>/fixtures/ candidates before cwd-relative fallbacks, so a same-named path in the invoking cwd can't shadow the active config's materialized fixture. - README.md: fixed the httpx import missing from the runnable example snippet, and corrected the Operation Map section to describe it as currently-unused pass-through metadata (renderPythonSuite doesn't read it). - tests/codegen/python-sdk-emitter.test.ts: added a temp-directory test for materializePythonFixtures (copy, missing-source, and overwrite-not-merge behavior), and updated the extract-call assertion for the new _MISSING-gated ctx.set shape. Declined: query-parameter support in RequestStep/emitter (cross-cutting path-analyser modeling gap, already tracked as follow-up in an earlier review thread) and propagating a hard-fail from a skipped empty-requestPlan scenario in materializer/src/index.ts's runForTarget (shared across all emitter targets, out of scope for this python-sdk-only PR). Signed-off-by: yarm03 <jwoconnor03@gmail.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: yarm03 <jwoconnor03@gmail.com> --------- Signed-off-by: yarm03 <jwoconnor03@gmail.com> Co-authored-by: yarm03 <jwoconnor03@gmail.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: real-toolchain-validated csharp-sdk emitter fixes Split out of PR #573 (combined/sdk-emitter-fixes) into a standalone per-SDK PR. - Strongly-typed key-struct construction: CSHARP_PATH_PARAM_KEY_TYPE map + `.AssumeExists(...)` codegen for path params, falling back to a plain string binding for unmapped names (not every path param is a key struct, e.g. getUser's username). - StringValueObjectConverterFactory (TestFixtureBase.cs): reflection- detects any SDK key-struct type (Value property + AssumeExists/ IsValid) and (de)serializes it as a plain JSON string, fixing both request-body round-tripping and response field-path extraction. - ExtendedDeploymentResponse-style "Raw" wrapper unwrapping in ToJsonElement, so field-path extraction matches the real wire shape instead of the C#-ergonomic PascalCase mirror properties. - Fixture path resolution reuses TestFixtureBase's ResolveFixturePath() (was building an inline, less-robust path assuming fixtures/ sits next to the test binary). - Missing operationId -> request-DTO mappings (createDocumentLink, deleteResource, resolveIncident, modifyProcessInstance, several *Statistics reads, etc.) and corrected 3 pre-existing wrong mappings. - CSHARP_VOID_METHODS: bare-Task-returning SDK methods (GetStatusAsync, ResetClockAsync, ~48 Assign*/Unassign*/Delete* RBAC methods, etc.) no longer get an implicitly-typed `var result =` assignment (CS0815). - JSON body rendering gained omitWhenUnbound awareness (previously only the multipart-fields loop had it) via partitionJsonFields/collectJsonRequestFields/emitJsonRequestDataLines. - renderTenantExpr uses GetStringBindingOrNull (deployment tenantId is always broker-optional) instead of a throwing RequireStringBinding. - eventualWaitsAfter witness-polling: AwaitEventuallyWitness + WitnessPredicateMatches (TestFixtureBase.cs) plus emitter wiring at all 3 success-path exits, mirroring the js-sdk emitter's pattern. - Repo housekeeping swept in from the original combined branch's history (unrelated to any single SDK, folded in here rather than a separate PR): hashicorp/vault-action v3->v4 + create-github-app-token v2->v3 bumps, .dockerignore, README's @camunda8/c8ctl -> @camunda8/cli package-name fix, spec-pin bump, and a run-hub.sh fix so a generate-only STEPS run no longer requires a live Hub/Keycloak. * fix: pass nonPublic:true to Activator.CreateInstance for StringValueObjectConverter<T> (Copilot PR #576 review) StringValueObjectConverter<T> is a private nested class; Activator.CreateInstance(Type) only probes public constructors by default. Pass nonPublic: true so the converter factory reliably works regardless of compiler/runtime-specific implicit-constructor accessibility behavior. * fix(csharp-sdk): add missing statistics/path-param SDK type mappings; fix curl-only fixture gate - CSHARP_REQUEST_TYPE_BY_OPERATION: add getProcessInstanceStatisticsByDefinition, getProcessDefinitionInstanceVersionStatistics, getJobErrorStatistics, getJobTimeSeriesStatistics, getJobWorkerStatistics (verified against the camunda/orchestration-cluster-api-csharp 9.2.2 source/docs) so generated no-body statistics calls no longer compile with CS7036. - CSHARP_PATH_PARAM_KEY_TYPE: add authorizationKey, batchOperationKey, decisionEvaluationInstanceKey, decisionEvaluationKey, and adHocSubProcessInstanceKey (typed as ElementInstanceKey per the SDK docs) so the generated calls pass the SDK's strongly-typed key structs instead of RequireStringBinding's raw string, fixing CS1503. - scripts/e2e/run-hub.sh: provision resource fixtures when either `run` or `curl` is selected (not just `run`), so a curl-only invocation still exports RV_FIXTURE_* and the curl oracle validates the intended 400s instead of surfacing 404/403 mismatches on filler keys. Addresses Copilot PR review threads on emitter.ts:82 and emitter.ts:104, and the suppressed advisory on scripts/e2e/run-hub.sh:70. Signed-off-by: johnOC03 <jwoconnor03@gmail.com> * fix(csharp-sdk): make RequireStringBinding return non-nullable string RequireStringBinding used RequireBinding, which already guarantees a non-null value, but the helper's return type was string?. Under <Nullable>enable</Nullable> this propagated spurious nullable warnings into generated SDK calls and AssumeExists(string) call sites. Change the return type to non-nullable string and use the null-forgiving operator on ToString() to match the real (non-null) contract. Addresses Copilot PR #576 review suppressed advisory. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: copilot-swe-agent[bot] <copilot-swe-agent[bot]@users.noreply.github.com> * fix(csharp-sdk): dedupe CsharpOperationMap types by importing from sdk-mapping emitter.ts re-declared CsharpOperationMapEntry/CsharpOperationMap locally even though sdk-mapping.ts already defines and exports the same types. Import and re-export them from sdk-mapping.js instead so there is a single source of truth; consumers (index.ts, tests) keep importing from emitter.ts unchanged. Addresses suppressed Copilot review advisory in PR #576. Signed-off-by: Copilot <copilot-swe-agent@users.noreply.github.com> * fix(csharp-sdk): honor pathParams alias in client calls; fix curl-compare string body encoding - renderClientCallForPath now consults RequestStep.pathParams[].var (the planner/emitter alias contract already honored by the JS SDK emitter's buildJavaScriptUrlExpression) instead of always deriving the ctx binding name from the URL param name, so an aliased path param resolves to the correct context value. - curl_compare.py no longer JSON.stringify()s a primitive-string request body, which previously double-quoted the wire payload compared to what Playwright's request.data actually sends for a plain string. Addresses two Copilot PR #576 suppressed advisories (nano-ack threads posted separately). Signed-off-by: nano-agent <nano-agent@users.noreply.github.com> --------- Signed-off-by: johnOC03 <jwoconnor03@gmail.com> Signed-off-by: copilot-swe-agent[bot] <copilot-swe-agent[bot]@users.noreply.github.com> Signed-off-by: Copilot <copilot-swe-agent@users.noreply.github.com> Signed-off-by: nano-agent <nano-agent@users.noreply.github.com> Co-authored-by: yarm03 <jwoconnor03@gmail.com> Co-authored-by: copilot-swe-agent[bot] <copilot-swe-agent[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot <copilot-swe-agent@users.noreply.github.com> Co-authored-by: nano-agent <nano-agent@users.noreply.github.com>
Summary
A combined series of fixes across all three SDK emitters (
js-sdk,python-sdk,csharp-sdk), each found and verified by actually running the real generated toolchain (npm install+vitest,poetry install+pytest,dotnet build+test) against a live broker — not just unit tests. Rebased cleanly onto currentmain(0 conflicts remaining after resolution); full local pre-push checklist (lint, all workspace typechecks, pipeline regeneration,npm test) passes with 994 passed / 12 skipped / 0 failed.js-sdk
globalContextSeedswiring and agent-connector semantics (seedBinding emits literal '<default>' for tenantIdVar, causing HTTP 409 on every tenant producer #342).@@FILEfixture resolution and seed-binding support (previously silently dropped required fields).step.pathParamsdead-field bug — see repo history for the full class of this bug across emitters).tsconfig.jsonlibsetting soError(message, { cause })typechecks (ES2022.Error).python-sdk
@@FILEfixture bytes instead of passing an unresolved marker string.buildPythonUrlExpression's path-param derivation (same class of bug as js-sdk).csharp-sdk
Camunda.Orchestration.Sdk9.2.2 surface (via direct assembly reflection, not guesswork): strongly-typed key.AssumeExists(...)construction, correct request-DTO type names, bare-Task-returning method handling,RequireStringBindingforstring?SDK parameters, and 41 missing body-type mappings for search operations.CsharpOperationMaptype to allowreadonlyentry arrays (needed foras consttest fixtures).Rebase / cleanup
configs/camunda-oca/ontology/semantics.jsonsemantic-type entries thatmainhad independently reclassified during the time this branch was in flight (AgentDefinitionKey,AgentHistoryItemKey,LoopIterationId) — deferred tomain's already-reviewed classification rather than re-litigating.main's superiorscripts/e2e/run-oca.shpositive-suite-runner logic (properPW_FAILtracking) over this branch's now-superseded fix for the same bug.Camunda.Orchestration.Sdk9.2.2pin inCamundaIntegrationTests.csproj(verified as the current, correct version this branch's csharp-sdk work was built/tested against) overmain's stale9.0.0.Known blockers (not fixed here — flagged for follow-up)
Live-broker validation surfaced several pre-existing, cross-cutting issues that affect all three SDK targets roughly equally and are out of scope for an emitter-level fix in this PR:
docker/docker-compose.yml's broker has no multi-tenancy-enabling env var, but the scenario planner/domain-semantics unconditionally populates atenantIdfield oncreateDeployment(and other tenant-scoped ops). Confirmed directly:POST /deploymentswith atenantIdfield →400 INVALID_ARGUMENT "multi-tenancy is disabled"; the same call without it →200. This cascades into mostcreateTenant→createDeployment-chained scenarios across every SDK target.docker/docker-compose.multi-tenancy.ymloverlay (mirroring the existingdocker-compose.rbac.ymlpattern) that enables multi-tenancy, so it can be exercised without changing the default broker's behavior.tenantIdinjection per-config when multi-tenancy is known to be disabled.docker compose down -v && up -d; a plainrestartdoes not reliably clear the H2 in-memory store) and report clean before/after counts.unassign*/delete*RBAC operations chain straight fromcreate*without the intermediateassign*step, producing legitimate 404s. Same root cause likely affectscorrelateMessage/broadcastSignalscenarios missing a precedingcreateDeployment+createProcessInstancestep.waitUpToMs: 0hardcoded for js-sdk's eventually-consistent calls — defeats the SDK's own consistency-retry mechanism, a likely contributor to timing-raceNOT_FOUNDs on plain create-then-get chains.getGlobalJobStatistics,getUsageMetrics) need query-parameter modeling in path-analyser (not just a request-body object) to compile — same gap affects js-sdk'sgetUsageMetrics. Also several response-shape/field-path-extraction gaps and polymorphic/oneOf request-body types remain unresolved for a handful of operations.None of these are regressions from this PR — they were present before this branch's work began and are documented in detail (with repro steps) for follow-up.