Skip to content

blocks-generate-spec silently assigns schemas across namespaces that share a method name #445

Description

@costleya

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

  1. Clone the reproduction repository.
  2. Run pnpm install.
  3. Run pnpm repro.
  4. The control assertions confirm that both direct, qualified methods exist in the generated OpenRPC document.
  5. 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)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions