The specification · Normative text

The value domain

Normative draft. What a fold produces. Rules are F-Val-*: the value domain is a property of folding’s output so it takes the F-* prefix. Derived from FoldedValue and its cases (fold.ts), FoldableFunction (fold.ts), carriesLiveObject (fold.ts), isFoldSymbolicEnvelope, and reviveFoldedValue (fold-import.ts), at e4074c17.


F-Val-Domain (the closed union)

A folded value v is exactly one of:

CaseFormRow
scalarstring | number | boolean | null | undefinedL4.1
arrayv[]L4.1
object{ [k: string]: v } with prototype ObjectL4.1
attribute reference{ __attrRef: { entity, attribute } }L3.9
intrinsic, tag form{ __intrinsic, strings: string[], values: v[] }L6.3
intrinsic, call form{ __intrinsic, args: v[] }L6.3
helper call{ __helper, args: v[] }L6.4
resource{ __resource, props, attributes?, args? }L6.6
composite step{ __compositeStep, args: v[] }L6.5
symbol{ __symbol: string }L6.2

Nothing else. In particular no function is a v (F-Val-Callable), and no live instance is a v, a live instance is what an envelope becomes (F-Val-Live).

F-Val-Envelope (six envelopes recognized by key)

A value is an envelope iff it is a non-array object carrying one of the keys __attrRef, __intrinsic, __helper, __resource, __compositeStep, __symbol (isFoldSymbolicEnvelope). An envelope is a finished value that denotes something not yet constructed; it is never a thunk and an implementation must not attempt to force it.

F-Val-Fate (what happens to each envelope)

Revival (reviveFoldedValue) walks a folded tree and replaces each envelope by its fate.

EnvelopeFate
__attrRefpasses through unchanged — the serializer accepts the plain envelope (L6.7)
__intrinsic (both forms)revived: the intrinsic function the file’s import names is invoked — Name(strings, ...values) or Name(...args) (L6.3)
__helperrevived: the helper the file’s import names is invoked with the revived args (L6.4)
__resourcerevived: the class the file’s import names is constructed — from props/attributes, or by spreading args when present (L6.6, F-Val-Arity)
__compositeSteprevived: the composite is resolved (J2 F-Call), then .step is read off the real result (L6.5)
__symbolrevived: the text must match a simple dotted chain; its root resolves through the file’s imports and the rest is real property access (L6.2)

In the data-host profile (F-Profile-DataHost, objective.md) none of these fates runs. Revival is serialization, every envelope is the output, and the host’s serialization mapping is what turns it into the artifact.

Exactly one envelope survives to serialization: __attrRef. The other five must never reach a serializer. An implementation that emits a __resource envelope has produced wrong output.

F-Val-Position (validity is position-dependent)

Revival carries a flag requireLiveRefs.

So the same v is valid in one position and a rejection in another. A specification of the domain alone does not capture this; the rule is part of the domain.

F-Val-Live (liveness)

A value carries a live object iff it, or anything reachable through plain objects and arrays, has a prototype other than Object, Array, or null, or is a function (carriesLiveObject, L4.5).

Live objects reached through cross-file resolution, an AttrRef instance, a Declarable, a CompositeInstance, an Intrinsic instance, pass through revival unchanged (L6.1). The generic walk would rebuild a plain copy and destroy the identity J3 exists to preserve. isIntrinsic is keyed on a global Symbol.for so this holds across separately loaded copies of the core.

Liveness is the entity test of F-Identity (J3) and is what F-CallLeak tests. F-Capture and F-Import test something broader; F-Identity says which is normative and what the difference costs.

F-Val-Callable (functions are callable)

FoldableFunction is a marker for a project-local function J1 may call. It is not a v, never appears inside a folded tree, and is refused anywhere a value is required: { resolver: φ } does not fold though φ(x) does (L3.1, L4.4). An eagerly-evaluated lexicon function referenced without calling it is refused likewise with “call it instead” (L3.17).

F-Val-Serializable (the sub-domain that may reach output)

A value may reach a serializer iff it is: a scalar; an array or object all of whose members may; an __attrRef envelope; or a live instance produced by revival. Every other envelope, and every callable, may not. The objective’s “byte-identical serialized output” is stated over this sub-domain.

F-Val-Arity (a resource’s constructor arguments)

__resource.props is the first object-literal argument. When the argument list is not (props) or (props, attributes), e.g. new Parameter("String", {…}), args is present and authoritative: the entity is constructed by spreading it, and props is reported for readers but never re-passed (L4.2).

F-Val-Undefined (undefined in the domain)

undefined is a scalar of the domain (L4.3). A property whose value is undefined is present in the folded namespace, with that value, and a spread copies it like any other own entry. The namespace therefore keeps the distinction between an absent key and an undefined one which a consumer whose contract is selective-by-omission depends on.

Emission is where the key is dropped, and an undefined array element becomes null there, for both JSON and YAML, because YAML is round-tripped through the JSON emitter. For a lexicon that serializes YAML itself, the rule is that serializer’s own.

F-Val-Symbol-Scope (where __symbol may appear)

Only inside an intrinsic’s interior, a tag’s interpolations or a call form’s arguments (foldIntrinsicValue). Anywhere else an unresolved chain is a located rejection (F-Reference, J2). A __symbol therefore never appears at the top of a folded tree.

F-Val-Source (every value has a source form)

For every value v of F-Val-Domain there is source in the subset (grammar.md) whose fold, in the profile named, is v. The table names one such form per case which is the form a generator should prefer and not the only one the subset admits.

CaseA source form that folds to itIn
scalarthe literal: "s", 1.5, true, null, undefined (F-Eval-Literal, F-Eval-Undefined)both
array, objectthe literal, with each member’s source form as its element or value; a key that is not an identifier is a string key (S-Prop)both
attribute reference {__attrRef: {entity, attribute}}entity.attribute, where entity is a same-file const bound to a new (F-Eval-Member step 1, F-Prebuild)both
intrinsic, tag form {__intrinsic, strings, values}the tagged template name\s₀${v₀}s₁…`` for a registered tag (F-Eval-Tagged)both
intrinsic, call form {__intrinsic, args}name(args…) for an intrinsic registered with foldsAsCall (F-Eval-CallIntrinsic)both
helper call {__helper, args}name(args…) for a registered helper (F-Eval-CallHelper). J1 yields the envelope and revival invokes the helper, so the envelope is never the outputfull
resource {__resource, props, attributes?, args?}new C(props), new C(props, attributes), or new C(…args) when args is present (F-Eval-New, F-Val-Arity)both
composite step {__compositeStep, args}the interpretable factory form (F-Host-Composite)full
symbol {__symbol}the unresolved chain, written inside an intrinsic’s interior (F-Eval-Interior, F-Val-Symbol-Scope)both

Nothing else is a value, so nothing else needs a form, and the fold of a form in full is the value after F-Val-Fate, so a resource’s form folds to a live instance there and to the envelope in data-host; the round trip (README.md, “the generator obligation”) is stated per profile for that reason. A live instance has no source form of its own: its form is the envelope’s.