Skip to content

Code-only blueprint updates unexpectedly rewrite binding metadata #147

Description

@ai-yang

Suggested severity

High (S2): the dedicated code-only update path can silently change a published blueprint's connection contract, or fail for an unrelated binding-annotation error.

Summary

The Overseer.updateBlueprint() API documents updateCode and updateBindings as independent options. However, calling updateBlueprint(id, { updateCode: true }) currently calls collectBindingMetadata() and assigns its result to record.metadata.bindings.

The Workshop's Update code action invokes exactly this code-only option, so the normal UI workflow is affected.

Deterministic reproduction on unpatched main

Using commit 8b08672050e6957987a87b5f9cab2801ba89b42c:

  1. Create a blueprint record whose stored binding metadata is OLD_API.
  2. Make the source gadget's current binding metadata return NEW_API.
  3. Call updateBlueprint("blueprint-1", { updateCode: true }).
  4. Inspect record.metadata.bindings and the collectBindingMetadata() call count.

Actual result

collectBindingMetadata() is called once and the stored metadata changes from OLD_API to NEW_API, even though updateBindings was not requested.

If collection throws because a current binding annotation is incomplete, the code-only update rejects before snapshotting the code. The controlled regression test failed on unpatched main with promise rejected "Error: invalid current binding annotation" instead of resolving.

Expected result

A code-only update should update the code snapshot/version and preserve the blueprint's existing binding metadata. Binding metadata should be read and replaced only when updateBindings: true is present.

Impact

  • A user choosing Update code can unintentionally change the inputs/connections required by an already published blueprint.
  • An invalid current binding annotation can block an otherwise valid code-only update.
  • The behavior contradicts the public RPC contract, which assigns binding refresh to the independent updateBindings option.

Root cause

updateBlueprint() enters one shared if (options.updateCode || options.updateBindings) block and unconditionally collects and assigns bindings before checking options.updateCode.

Proposed fix

Keep the shared gadget-ID resolution, but guard collection and assignment with if (options.updateBindings). Cover code-only, bindings-only, and combined updates.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions