Skip to content

5. Names and Bindings

5.1 Name Domains

Culsma uses distinct name domains:

  1. Include targets (source file loading domain)
  2. Library import targets (frontend library-resolution domain)
  3. Module names (source-file and imported-library namespace domain)
  4. Protocol names (program structure domain)
  5. Protocol parameter names (call-frame domain)
  6. Local let names (expression domain)
  7. Semantic/material names (runtime container/group binding domain)
  8. Content identities and aliases (runtime content domain)

These domains have different resolution rules and diagnostics.

5.2 Include Target Resolution

include target resolution is a load-stage concern.

Rules:

  1. Source-level include targets resolve from including file directory (relative) or absolute path.
  2. Loader resolves include targets recursively and deduplicates canonical paths.
  3. Missing targets and include cycles are load-stage hard errors.

5.2.1 Library Import Target Resolution

import target resolution is a frontend-resolution concern.

Rules:

  1. Import targets resolve against configured library roots and bundled stdlib sources.
  2. Missing import targets are frontend-resolution hard errors.
  3. Imported library protocols join the active program namespace before compilation.
  4. Bundled standard-library protocols remain separately available for protocol resolution and do not join the active program namespace.

5.3 Protocol Name Resolution

Protocol reference baseline:

  1. Preferred protocol reference syntax is Module.Protocol(...).
  2. Module resolves to source module name (default: file stem in loader) or imported library module name.
  3. Protocol resolves from the active program namespace or the separately available bundled standard-library definitions.
  4. include ProtocolName; remains part of the source surface.

Rules:

  1. Resolution is exact and case-sensitive.
  2. Missing targets are reported as PLAN_UNKNOWN_REFERENCE.
  3. Cyclic reference chains are reported as PLAN_REFERENCE_CYCLE.
  4. Diagnostic code definitions and stage placement are specified in Chapter 8.
  5. Duplicate module names across loaded files are load-stage hard errors.

5.3.1 Protocol Parameter Binding

Rules:

  1. Plan lowering binds protocol-call arguments to callee parameters in a dedicated call frame.
  2. Binding precedence: explicit arg > default value.
  3. Missing, unknown, or duplicate protocol-call arguments are reported as PLAN_CALL_ARG_MISSING, PLAN_CALL_ARG_UNKNOWN, or PLAN_CALL_ARG_DUPLICATE.
  4. Diagnostic code definitions and stage placement are specified in Chapter 8.
  5. Parameter names MUST be unique within one protocol declaration.
  6. Parameter names and local let names in the same protocol body MUST NOT conflict.
  7. Parameter names and repeat binding names in the same protocol body MUST NOT conflict.
  8. Callee parameter binding does not implicitly inherit caller let bindings.
  9. The call contract is name-based; examples SHOULD use named arguments.
  10. The parser may normalize positional protocol-call arguments as arg0, arg1, ... but those names are not the preferred stable binding contract.

5.4 Local Binding Resolution (let)

let names are resolved in protocol-local context. A protocol has one root name scope. Nested statement bodies create child scopes for lookup, but they do not permit shadowing; local let declarations remain protocol-body unique as specified below.

Rules:

  1. IDENTIFIER expressions MAY resolve to prior let bindings in the same protocol-local context.
  2. Protocol parameter names also enter the callee protocol-local binding context.
  3. if, repeat, with env, and with constraint bodies create nested lookup scopes.
  4. Nested lookup scopes MAY read and assign names declared in an enclosing protocol-local scope.
  5. Nested lookup scopes MUST NOT redeclare an existing protocol parameter, local let name, or repeat binding visible from an enclosing scope.
  6. Local let names MUST be unique within one protocol body, including all nested statement bodies and both branches of an if.
  7. A repeat binding is visible only inside its repeat body. It MUST NOT shadow a protocol parameter, a local let name, or an active enclosing repeat binding. Sibling repeat statements whose scopes do not overlap MAY reuse the same binding name.
  8. The same parameter or local names MAY be reused in different protocol declarations, including imported or indirectly imported protocols.
  9. repeat and if compile-time checks rely on resolvable local values.
  10. Unresolvable compile-time requirements (for example, non-constant repeat count) are compile errors.

Runtime-controlled mutation of protocol-local scalar state is supported. Assignments to an existing protocol-local binding inside a runtime-controlled block update that binding for subsequent runtime evaluation along the executed control-flow path.

5.5 Semantic Name Resolution (Material Domain)

Step arguments that denote containers/material names resolve via runtime binding state.

Resolution model:

  1. Names resolve to container_id through binding tables and direct container references.
  2. container_id is a logical vessel identity; in real lab setup it typically maps to a tube or a plate well.
  3. Physical carrier mapping is represented by container metadata (for example carrier kind/id/well), not by replacing the logical identity model.
  4. Material-name resolution is invariant across reporting and external inventory configuration.

Failure surface:

  1. Binding validation checks identifier-form source refs and string-literal source refs with the same binding rule.
  2. Binding coverage is attached to source IR: mutation_stmt (<<) target/source, with env(...) inferred/explicit-hold targets, and sep/frac/img/ecp/phy sample refs.
  3. agit(...) sample refs are also covered by binding read checks.
  4. Unbound runtime-visible names are commonly reported as SEM_UNBOUND_NAME_REFERENCE when statically decidable or MAT_BINDING_NOT_FOUND during material execution.
  5. Diagnostic code definitions and stage placement are specified in Chapter 8.

5.5.1 Content Identity Resolution (Content Domain)

For content-aware constructor and load flows:

  1. Content symbols resolve to content_id in runtime content registry/state.
  2. content_id is independent of container_id; association is formed by load/apply operations.
  3. One content identity may appear in multiple containers over time through transfer/split/mix semantics.
  4. Container bindings and content bindings are distinct domains and must not be conflated.

Failure surface:

  1. Constructor contracts validate content identity fields before runtime use.
  2. Missing or invalid content constructor fields are commonly reported with SEM_MISSING_CONTENT_KIND or SEM_INVALID_CONTENT_KIND.
  3. Runtime/material execution fails with MAT_CONTENT_NOT_FOUND when a referenced content identity is absent.
  4. Diagnostic code definitions and stage placement are specified in Chapter 8.

5.5.2 Component-Entry Identity and Selection

Each normalized material component entry has a non-empty entry_id that is unique within its containing container. entry_id identifies that physical entry; content_ref independently identifies what the entry contains. Two entries may therefore share one content_ref while retaining different entry_id values and material relations.

The read-only ContainerRef.materials selector surface used by sep.transitions supports:

  1. container.materials[index], where index is a statically decidable non-negative integer into the normalized ordered live-entry list;
  2. container.materials.get(entry_id), where entry_id is a statically decidable non-empty string matched exactly within the same live-entry list.

get(entry_id) does not match content_ref, labels, or prefixes and does not fall back to another same-content entry. Local aliases may supply the selector or key when plan lowering can resolve them statically.

For runtime-generated identities, the unsuffixed content_ref is used when available. A collision uses the first available underscore ordinal, such as DNA_1, DNA_2, and so on. Existing IDs are opaque and are not rewritten; for example, a pre-existing DNA::1 remains valid but is not newly generated by this rule.

5.6 Cross-Protocol Binding Boundary

Rules:

  1. let is protocol-local.
  2. Each protocol declaration has an independent local name scope.
  3. Protocol parameter bindings are protocol-local to the callee frame created during plan lowering.
  4. File includes and library imports add protocol declarations to the active program namespace; they do not merge local let, parameter, or repeat-binding names across protocols.
  5. A protocol may reuse parameter and local names that are already used in another protocol, including imported or indirectly imported protocols.
  6. Material bindings are global within one runtime run.
  7. Protocol reference expansion does not create a separate runtime material namespace.
  8. Content identities are also global within one runtime run and follow runtime/event-log lifecycle.
  9. include ProtocolName; inlining and qualified protocol-reference expansion both expose the referenced protocol body to downstream plan-time processing.
  10. Protocol-local let bindings do not automatically cross protocol boundaries.
  11. Under strict binding validation, referenced/include'd protocol bodies may still contribute runtime-visible constructor-defined names to downstream semantic read checks.
  12. Protocol boundary exports follow the Chapter 9 protocol-return projection rules; returned runtime references do not collapse to plain text names.

5.7 Conformance Boundary

Frontend, planner, validator, typechecker, and runtime implementations must conform to these binding rules; they do not define separate binding behavior.

Public language reference for the current Culsma surface.