Skip to content

blueprint §5.3: ERR_UNBOUND_PARAMETER is undecidable — and false-positives — when a node uses a published reference #38

Description

@justinmerrell

Summary

Blueprint §5.3 (added in #36) makes ERR_UNBOUND_PARAMETER a negative existence claim — "a key matching no USER input of any node" — that an offline implementation cannot substantiate when the graph contains a published (UUID) component reference, and the reference implementation currently false-positives in exactly that case.

The mechanism

In tools/src/semantic.ts, checkGraphAgainstItem resolves only repo-local references into resolved (a published reference "belongs to the capability phase"), and checkParameterBinding then builds its declared input-key map from resolved alone:

const component = resolved.get(node)
// A published reference resolves in the capability phase, so its inputs are
// unreadable here. §5.3: an implementation MUST NOT report an input it was
// never given the means to read.
if (component === undefined) continue

The unbound loop that follows has no such guard: any parameter key absent from declared is reported at /spec/parameters/<key> as "names no USER input of any node" — including a parameter whose only covered input lives on the node the validator just declined to read.

Why this is a defect rather than the documented behaviour

§5.3 closes with:

A blueprint mixing the two forms is checked against the repo-local half and no further, and an implementation MUST NOT report an input it was never given the means to read.

That principle is honoured for ERR_UNCOVERED_REQUIRED_INPUT (an unreadable node's inputs never enter the coverage set) and for ERR_INCOMPATIBLE_PARAMETER_TYPE (nothing to compare). But ERR_UNBOUND_PARAMETER is the same principle's mirror image on the parameter side: the diagnostic asserts a fact about every node's inputs while the implementation was given the means to read only some of them. A valid blueprint —

spec:
  components:
    db:
      component: ./components/postgres.yaml   # local
      size: general.standard.small
      connections: {}
    api:
      component: 018f6b3a-…                   # published; declares USER input `apiKey`
      size: general.standard.small
      connections: {}
  parameters:
    apiKey:            # binds the published node's input — and only that
      schema: {type: STRING, isSensitive: true}
      isRequired: true
      ui: {label: API key}

— is rejected in the semantic phase with ERR_UNBOUND_PARAMETER at /spec/parameters/apiKey, even though the parameter is legitimately bound. The document then cannot be made valid offline at all except by removing the parameter, which under §5.3's second rule may in turn be the wrong move (the published component's required input still needs a guaranteed value at deploy).

Proposed fix

Suppress ERR_UNBOUND_PARAMETER when the graph contains at least one reference the implementation could not read (a published reference, or a local reference already carrying its own ERR_COMPONENT_NOT_FOUND). The claim becomes decidable again only when every node's inputs were readable. Options, in order of preference:

  1. Wholesale suppression — one sentence in §5.3 ("where any node's component is unreadable, an implementation MUST NOT report ERR_UNBOUND_PARAMETER"), one if in checkParameterBinding. Symmetric with how the other two rules already degrade.
  2. Re-classify the unbound check as capability for mixed graphs — heavier, and splits one rule across two phases.

Every existing corpus fixture tree is all-local, so option 1 changes no current case; a new fixture pinning the mixed-form pass case would close the gap.

A minor adjacent observation (fold in or ignore)

§4.4 presents cpuDedication: shared and dedicated as symmetric pins. On the platform, placement maps only "dedicated" to a host capability (placement/domain/compute_constraints.pystrict=True over {"dedicated": CPU_DEDICATED}), so shared narrows nothing today. Either wording that makes shared explicitly "the default made explicit", or a platform-side must-be-shared capability, would remove an asymmetry a reader cannot see.

Found while reviewing #35/#36 for platform alignment (musher-dev/platform is bumping its conformance pin to 311be77 and implementing the new rules; its implementation will apply the option-1 guard in the meantime).

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