Before opening, please confirm
Which package(s) are affected?
@aws-blocks/core
Describe the bug
blocks-generate-spec can silently assign one namespace's parameter and result schemas to another namespace when both namespaces expose an operation with the same bare method name.
In the reproduction, both namespaces are direct top-level ApiNamespace exports and both qualified OpenRPC methods are discovered:
widgets.create
subscriptions.create
Their TypeScript contracts are deliberately incompatible. Nevertheless, the generated OpenRPC entry for widgets.create receives the email input and numeric subscriptionId result belonging to subscriptions.create, rather than its own label input and string widgetId result.
Unlike an unknown fallback, this produces a valid-looking but incorrect OpenRPC document. Downstream documentation, validators, and generated native clients can therefore trust the wrong contract without an obvious generation failure.
Expected behavior
Extracted method schemas must be associated with the fully qualified RPC method name, including the namespace.
For the reproduction:
widgets.create must retain CreateWidgetInput and CreateWidgetResult.
subscriptions.create must retain CreateSubscriptionInput and CreateSubscriptionResult.
The schema for one namespace must never overwrite or be reused by another namespace merely because both operation names are create.
Reproduction steps
Minimal reproduction: https://github.com/costleya/aws-blocks-method-schema-collision-repro
- Clone the reproduction repository.
- Run
pnpm install.
- Run
pnpm repro.
- The control assertions confirm that both direct, qualified methods exist in the generated OpenRPC document.
- The final assertion fails because
widgets.create contains subscriptions.create's incompatible parameter and result schemas.
Code snippet
import { ApiNamespace, Scope } from '@aws-blocks/blocks'
const scope = new Scope('method-schema-collision-repro')
interface CreateWidgetInput {
label: string
}
interface CreateWidgetResult {
widgetId: string
}
interface CreateSubscriptionInput {
email: string
}
interface CreateSubscriptionResult {
subscriptionId: number
}
export const widgets = new ApiNamespace(scope, 'widgets', () => ({
async create(input: CreateWidgetInput): Promise<CreateWidgetResult> {
return { widgetId: input.label }
},
}))
export const subscriptions = new ApiNamespace(scope, 'subscriptions', () => ({
async create(
input: CreateSubscriptionInput,
): Promise<CreateSubscriptionResult> {
return { subscriptionId: input.email.length }
},
}))
Log output
AssertionError [ERR_ASSERTION]: BUG: widgets.create should retain its own schemas rather than subscriptions.create schemas
widgets.create actual input:
{ email: string }
widgets.create expected input:
{ label: string }
widgets.create actual result:
{ subscriptionId: number }
widgets.create expected result:
{ widgetId: string }
Environment
- AWS Blocks version:
@aws-blocks/blocks@0.2.7, @aws-blocks/core@0.1.18
- Node.js version:
24.18.0
- Package manager (npm/yarn/pnpm):
pnpm@11.24.0
- OS: macOS
- CDK version (if applicable): N/A
Additional context
The current implementation extracts schemas into a map keyed by the bare method name and later looks them up using that same bare name, even though generateSpec has already computed the qualified method name. Relevant source:
The minimal safe association is the fully qualified method name, such as widgets.create and subscriptions.create. Falling back to an unqualified create key would remain ambiguous and could preserve the silent corruption.
Suggested fix
Key extracted method information by the fully qualified RPC method name, for example widgets.create and subscriptions.create, and have generateSpec look up only the qualified name it already computes.
There should be no fallback to a bare method name. Every generated RPC method belongs to a namespace, and a fallback such as create is inherently ambiguous even when a particular application currently has only one matching operation.
Suggested regression coverage:
- two direct top-level namespaces with the same
create method name;
- intentionally incompatible parameter and result types;
- assertions that both qualified methods retain their own schemas regardless of declaration/traversal order;
- an assertion that the extractor does not emit or consume a bare
create schema key.
Validated patch
A working pnpm patch against @aws-blocks/core@0.1.18 is available on the codex/proposed-fix branch. Its pnpm-workspace.yaml registers the issue-specific @aws-blocks/core@0.1.18 patch.
It patches both the TypeScript source and the shipped dist implementation. The essential change is to qualify schemas at extraction time and use the already-computed qualified name at generation time:
- result.set(methodName, info)
+ result.set(`${namespaceName}.${methodName}`, info)
const qualifiedName = `${routingName}.${methodName}`
- const tsInfo = tsTypes.get(methodName)
+ const tsInfo = tsTypes.get(qualifiedName)
Before opening, please confirm
Which package(s) are affected?
@aws-blocks/core
Describe the bug
blocks-generate-speccan silently assign one namespace's parameter and result schemas to another namespace when both namespaces expose an operation with the same bare method name.In the reproduction, both namespaces are direct top-level
ApiNamespaceexports and both qualified OpenRPC methods are discovered:widgets.createsubscriptions.createTheir TypeScript contracts are deliberately incompatible. Nevertheless, the generated OpenRPC entry for
widgets.createreceives theemailinput and numericsubscriptionIdresult belonging tosubscriptions.create, rather than its ownlabelinput and stringwidgetIdresult.Unlike an
unknownfallback, this produces a valid-looking but incorrect OpenRPC document. Downstream documentation, validators, and generated native clients can therefore trust the wrong contract without an obvious generation failure.Expected behavior
Extracted method schemas must be associated with the fully qualified RPC method name, including the namespace.
For the reproduction:
widgets.createmust retainCreateWidgetInputandCreateWidgetResult.subscriptions.createmust retainCreateSubscriptionInputandCreateSubscriptionResult.The schema for one namespace must never overwrite or be reused by another namespace merely because both operation names are
create.Reproduction steps
Minimal reproduction: https://github.com/costleya/aws-blocks-method-schema-collision-repro
pnpm install.pnpm repro.widgets.createcontainssubscriptions.create's incompatible parameter and result schemas.Code snippet
Log output
Environment
@aws-blocks/blocks@0.2.7,@aws-blocks/core@0.1.1824.18.0pnpm@11.24.0Additional context
The current implementation extracts schemas into a map keyed by the bare method name and later looks them up using that same bare name, even though
generateSpechas already computed the qualified method name. Relevant source:extract-ts-types.tsgenerate-spec.tsThe minimal safe association is the fully qualified method name, such as
widgets.createandsubscriptions.create. Falling back to an unqualifiedcreatekey would remain ambiguous and could preserve the silent corruption.Suggested fix
Key extracted method information by the fully qualified RPC method name, for example
widgets.createandsubscriptions.create, and havegenerateSpeclook up only the qualified name it already computes.There should be no fallback to a bare method name. Every generated RPC method belongs to a namespace, and a fallback such as
createis inherently ambiguous even when a particular application currently has only one matching operation.Suggested regression coverage:
createmethod name;createschema key.Validated patch
A working pnpm patch against
@aws-blocks/core@0.1.18is available on thecodex/proposed-fixbranch. Itspnpm-workspace.yamlregisters the issue-specific@aws-blocks/core@0.1.18patch.It patches both the TypeScript source and the shipped
distimplementation. The essential change is to qualify schemas at extraction time and use the already-computed qualified name at generation time: