Skip to content

Outer selection and arithmetic contracts

Outer root values, nested record/array elements, named projections and read/write arguments share PlasmDataValue::Expression and ValueOperation. Its transfer algebra uses ValueContract::{join, arithmetic, data_value, field}; it is separate from Monty's inner Python semantics. The runtime evaluates that closed algebra over materialized values, without coercing heterogeneous branches into one storage type.

Conditional selection

a if boolean_expression else b has the union of the complete contracts of a and b. Equal contracts collapse; nested unions flatten; null adds nullability. Different domains remain distinct alternatives even when their storage types coincide. Records, arrays, observed presence and catalog pins survive recursively. Selection creates no entity authority. Only the selected branch is evaluated per row; both branches remain declared planning dependencies. A missing field is an error when evaluated, not a null value. Ordinary row comparisons retain three-valued null semantics: unknown selects the else branch. Explicit None tests are total predicates, as specified below.

Native value variants distinguish arrays from literal strings. "[1]" remains a string beside [1]; integers remain integers beside floating values. Projection, first/last, distinct and downstream typed compute preserve variants. Union numeric comparison/order is numeric, without rounding integers to floats; incomparable ordered values fail rather than sorting their serialized text.

Arithmetic transfer table

The table applies to storage kinds. Named profiles such as digit IDs and Unix timestamps retain their constraints on selection; arithmetic on their string or integer storage does not manufacture a new profile-conforming value.

Operands Operators Result
integer, integer + - * integer, checked i64
numeric, numeric / finite number
numeric, numeric with a number operand + - * finite number
string, string + string
money, money money_add(a,b), money_sub(a,b) money; currencies must match, including absence
money, integer or decimal string money_mul(value,factor), money_div(value,factor) money; retain currency; reject floats
internal Date, Date - whole elapsed days, truncated toward zero
all other pairs any admission error

Arithmetic distributes over union variants: every possible operand pair must be admitted. Nullable operands propagate null. Computed results have no source domain pin: bounds, enumeration membership, formatting profiles and other catalog constraints require validation against an explicitly chosen destination domain. Money retains its currency in the runtime value, not an unproved input domain. Money amounts use the existing bounded Decimal engine and its precision; this is not arbitrary precision rational arithmetic. Zero division, overflow, nonfinite results and currency mismatches fail before dependent effects.

len of a string, array or record returns an integer, propagating null; string length counts Unicode scalar values. It does not stringify arbitrary operands.

class Values(Program):
    @compute
    def retain(self, row: Row) -> Row.value:
        return row.value

    def build(self):
        rows = e1.query()
        choice = rows.select(value=lambda r: r.count if r.flag == True else r.text)
        increased = rows.select(value=lambda r: r.count + 1)
        return self.retain(choice), self.retain(increased)

Here choice.value retains the two domain alternatives; increased.value is a generic integer. To assert a destination constraint, return a served vN from a typed compute; its return validator proves that constraint before effects.

Executable evidence and limits

BC-03 property outer_values: 6 branch kinds × 6 branch kinds × both decisions, seven arithmetic/laziness cases, four negative-admission cases, four mixed-column composition cases, two structural record/array cases and nine arithmetic/type-failure effect gates. Expectations are fixture-authored. Core tests enumerate eight operand kinds × eight kinds × four operators × two nullability states, and verify domain-preserving joins and union distribution. Runtime tests cover exact integers, nested JSON versus strings, currency and numeric failures. These are finite products, not a claim of arbitrary constructor closure or new temporal-profile/duration algebra.

Recursive consumption

Immutable aliases reuse the same DAG node. Records (including {}), arrays, scalar literals, arithmetic, len, comparisons, boolean combinations and choices compose recursively. Projection lambdas bind their row cursor; root expressions use one explicit unit row. Arguments consume the resulting typed values; they do not reinterpret arithmetic as string syntax. A singleton field requires singleton proof, with a runtime empty check. Constructing a record confers no receiver identity or effect authority. Expression depth is bounded at 64.

Choice and boolean operands may reference declared dependencies, but cannot introduce calls that would be hoisted out of a lazy branch. Such calls fail admission explicitly; bind intended unconditional dependencies before the choice. Only the selected value branch is evaluated. This is not conditional DAG effect execution.

value_recursive_* matrix cases exercise roots, bindings, nested records/arrays, projections, read arguments, inline and bound write arguments, empty scoped records, membership and lazy errors. The arithmetic/type product above and its pre-effect gates continue to apply. The separate value_closure_* witnesses cover value annotations and structural compute contexts under the compute boundary contract.

Structural field selection

Field access recurses through typed records and observed records. A record union must declare the requested field in every variant; the result joins those field contracts. Optional presence is checked when the value is read and is not converted to nullability. A missing field and a present None remain distinct. The same selector works in root values, projections and capability arguments. Its dependency traversal retains the selected field's provenance, including sensitive-domain policy, without conferring receiver authority.

The value_closure_* live matrix and structural_field_selection_preserves_presence_and_union_types lock this rule.

Python branch evidence

Monty owns Python predicate semantics and control-flow narrowing. Plasm does not reinterpret None tests, Boolean composition, comparisons or isinstance using its own fact algebra. The synchronous monty-analysis::analyze_branches API observes typed subjects on both successors of a predicate without executing it.

Plasm maps those subjects to immutable captured DAG references. Lazy branches retain evidence only inside the selected scope; it cannot escape a merge or prove anything about a later, independently evaluated call. A selecting row filter applies true-branch evidence to its successor schema. The schema is re-derived from the reviewed predicate source and input contracts during plan validation.

The internal Refine value operation intersects evidence with the original contract and checks the materialized value. It preserves domain identity, temporal wire representation, recursive shape and field presence. It cannot grant receiver authority or turn an unobserved field into a present field. This is an IL operation, not taught Python syntax. Unsupported or unresolved analysis remains an explicit admission error.

Deferred dependencies are identified before lowering lazy successors. Each successor is lowered with its selected branch evidence, including when the lazy expression occurs inside a container. Materializing a Boolean port retains the original predicate as the static evidence source, while runtime evaluates it once. Logical capture provenance connects scalar bindings to their internal storage ports without exposing those ports to Python.

Executable witnesses include branch_refinement_is_shared_across_value_domains_and_nested_fields, datetime_nullable_guards_cover_scoped_and_recursive_consumers, and datetime_nullable_guards_reject_unproved_or_leaked_facts. These cover filter successors, lazy branches, nested records and rejection of leaked evidence.

Exact-money Python library

The arithmetic functions above and money_compare(left, right) are pure host bindings available in outer expressions and @compute bodies. Python executes calls and argument evaluation; Plasm's existing money kernel owns decimal arithmetic. Positional and keyword arguments are equivalent. Arithmetic results have the Money storage contract and currency, not the input's domain pin. Comparison returns -1/0/1 and accepts Money or an exact integer/decimal string on the right; currency conflicts fail. The normal destination validator applies before effects. Ordinary Python operators on the mapping carrier do not implement money arithmetic. No Decimal import or arithmetic dunder dispatch is implied. A compute turn allows at most 4096 host suspensions; memory, execution-time and output budgets still apply. The bindings cannot read catalogs, hydrate rows, or perform effects. Invalid arguments or arithmetic failures raise ValueError in Monty; uncaught exceptions fail the compute. A caught exception does not bypass destination validation.

Materialized contracts are a sound abstraction of the inferred type graph. Literal and truth-value exclusions that have no Plasm representation retain their enclosing positive type (for example str & ~Literal["skip"] becomes str). This cannot strengthen a proof or admit an unsafe access. Representable union and nullability refinements remain intact; unresolved positive types remain admission errors.