The specification · Normative text
The host interface
Normative draft. What a host supplies to make the subset mean
something and the rule by which each supplied thing is admitted. Rules are
F-Host-*. Derived from IntrinsicDef and the three fold predicates
(lexicon.ts), createResource/createProperty
(runtime.ts), FOLDABLE_AUTHORING_HELPERS and its admission
criteria (foldable-helpers.ts), isTrustedExecutableBinding, and
findCompositeDefinition, at e4074c17.
The extraction confirmed this list without amendment. The expression layer needed items 3, 4 and 5 and nothing else; items 1, 2 and 6 live in revival and interpretation, which the reference implementation does not yet port.
F-Host-Interface (what a host supplies)
Seven things. The first four are the parameters of the subset, the next two are what the trust rules need, and the last is the rules contract’s.
- Entity constructors. Classes whose instances are entities: built by
createResource(type, lexicon, attrMap),new (props, attributes?), orcreateProperty(type, lexicon),new (props). They carry a non-enumerable declarable marker,lexicon,entityType, and for propertieskind: "property". Revival constructs them (F-Val-Fate). A constructor holds the props it is given and exposes the attributesattrMapnames. It performs no I/O and no network call, reads no file and no environment name, and constructs no entity it was not handed. A host that validates or normalizes props does it in a rule. F-Rule-Pure says the same of rule code and F-Rule-Finding carries the finding to the author. - Attribute exposure.
attrMapnames the attributes an entity exposes; reading one on a live instance yields anAttrRefbound to that instance (F-Val-Live) and on a name yields the{__attrRef}envelope. - An intrinsic registry
ρ: a list ofIntrinsicDef. - An authoring-helper allowlist: names a call may fold through as a
{__helper}envelope. - A trust set: the package specifiers this build resolved and loaded (arm 1) plus the host’s own module tree (arm 2).
- A composite registration form,
export const N = Composite(fn, "N")withCompositeimported from the host, that makes a project-defined factory interpretable. - Rules: the host’s semantic checks over the folded namespace and the
artifact under the contract of
rules.md(F-Rule-Supply). A project may supply more as a policy.
In the data-host profile (F-Profile-DataHost, objective.md) a host is a
description rather than code: item 3, item 5, and a serialization mapping
that says what each envelope becomes in the artifact. Items 1, 2, 4 and 6
need something to invoke and are absent.
F-Host-Registry (the shape of an intrinsic registration)
IntrinsicDef = { name, isTag: boolean, foldsAsCall?: boolean, foldsEagerly?: boolean, … }isTagis required. An omitted value once defaulted silently to “not a tag” and shipped the most-used intrinsic in the ecosystem unfoldable. A registry entry that does not say which kind it is is invalid.isTag,foldsAsCallandfoldsEagerlyare mutually exclusive. The three fold predicates are: tag folds iffisTag; call form folds iff¬isTag ∧ foldsAsCall; eager folds iff¬isTag ∧ foldsEagerly.foldsAsCallandfoldsEagerlyare opt-in, per intrinsic, default off, and never inferred from the name, the tag flag, or the call’s shape (closed allowlists).- A registration is validated against the export it names, tagged
template signature versus plain call, and presence in the package’s own
exports (
chant dev check-lexicon). A host must provide the equivalent check or its registry is a claim.
F-Host-Admission (when a call may be registered)
A call-form intrinsic, an eager intrinsic, or an authoring helper qualifies only if all hold:
- it is a pure function of its arguments, no I/O, no environment read, no module-level mutable state, no observable side effect;
- it builds a deterministic envelope or plain data from them;
- invoking it at fold time is indistinguishable from invoking it during a real run of the file.
That third clause is the whole correctness argument for every registered
call, and it is the same-function principle: revival invokes the function the
file’s import names, never a reimplementation (F-Host-NoSubstitution).
Explicitly excluded by chant, with the reason each fails:
env(), which readsprocess.env.Op(), which returns an entity. An entity-returning factory is the nested-construction hazard wearing a call.propagate(),withDefaults(),resource(),mergeDefaults(), which are composite definition helpers, andpropagatemutates in place.createResource()andcreateProperty(), which build classes at a module’s top level, never as a value.
F-Host-Closed-vs-Open (why packages are closed and project files are open)
A call into a package folds only two ways:
- Through a closed allowlist, a registered intrinsic or helper, checked by name and by the provenance of the binding (F-Div-Provenance).
- At a declarator, by invocation (F-Call), reached directly or through a const alias (F-Declarator), where the result is a value whatever it is.
A call nested inside an expression never folds through a package. A call into a project file folds whenever the callee’s body is itself in the subset with no allowlist.
The asymmetry is the trust boundary. Package code is already loaded and executed by the build before discovery begins; admitting a call into it costs no execution the process was not performing so it is admitted by declaration and verified by registration. Project code is the untrusted input; it is admitted only when it can be evaluated without being executed, folded or interpreted, which a syntactic body check decides and an allowlist could not.
F-Host-NoSubstitution (the function that runs is the one imported)
For every registered name (helper, intrinsic, constructor, composite),
revival resolves the name through the folding file’s own import
bindings and invokes what it finds (F-Val-Fate, J2 F-Call). A host never
substitutes its own implementation for a registered name.
There are two consequences.
- A same-named function the file declared or imported from a project file is that function’s call (F-Eval-CallLocal). One imported from anywhere else is not the host’s and the file falls back (F-Div-Provenance).
- The registry cannot drift from the helpers’ real behavior because it never reimplements them.
This is the CTFE principle (prior-art.md) made a rule.
F-Host-Composite (the registration that admits interpretation)
A project file’s composite is interpretable (rule 2) iff its defining module
has export const N = Composite(fn, "N"), where Composite is bound, in
that module, to an import of the host’s own, fn is an arrow or function
expression, and the name argument is absent or a string literal.
A plain helper that returns a composite is not registered and stays on the
invoking path. A host that offers interpretation must define an equivalent
registration form. The shape of fn is S-FactoryBody.
F-Host-DataExports (a package’s plain data folds as values)
A named import from an active package resolves to the package’s real
export (J2 F-Import). A plain-data export, a pseudo-parameter namespace, an
action-constant table, folds as a value; a live Intrinsic instance passes
through revival unchanged (F-Val-Live). A namespace import of a package is
never resolved (F-Namespace) so nothing is reachable through ns.x from a
package: the class or intrinsic has to be reachable through a named
import.
F-Host-Trust (what may be imported during a fold)
Two arms, and nothing else:
- A specifier that is an active package of this build, or a subpath of one, matched by text against the closed set the build already resolved.
- A specifier that resolves to a path inside the host’s own module tree.
Text is insufficient here because an untrusted repository controls both
its source and its
node_modules.
A build with no package list keeps only arm 2. Under ι = isolated, an import
outside both arms is F-IsolatedRefusal (J2).
F-Host-Generality (what varies and what does not)
A host may vary the seven items of F-Host-Interface. It may not vary
the syntax (grammar.md), the semantics of admitted operators (J1), the
module system, the value domain’s shape (values.md), the judgments (J1–J3),
and the direction claim (divergence.md). Generality is over host
vocabularies (README.md, Scope). A host is one
instantiation; chant’s lexicons are the reference instantiation and the
reference implementation is meant to carry the interface without
any of them.