The executable contract for typed human input: clojure.spec over the
NORMALIZED form of a request, plus the closed vocabulary that form is built
from.
Two layers, one contract. com.blockether.vis.internal.human-input PARSES —
an extension (or a Python object arriving as JSON) writes is_required or
:is-required, "otp" or :otp, and the normalizer turns whatever came in
into one internal shape, naming the key its author has to fix when it cannot.
This namespace DECLARES that shape: every map is CLOSED, :name and :id
are the same identity, a :select really carries options, an :otp really
fits its boxes, and a :default really is a value of the field's own type.
Both are needed. A parser that only refuses bad INPUT still lets a bug
INSIDE the engine hand a surface a field with no :label, or hand a blocked
extension an answer with no :request-id — and that failure then surfaces as
a broken dialog three namespaces away from its cause. The specs are checked
once per request and once per answer, never per keystroke, so the guard sits
where it costs nothing.
The functions here only EXPLAIN (field-error, group-error,
request-error, answer-error return nil or a one-line reason); the
refusal itself stays
in human-input, which owns the error envelope every surface already
handles.
The executable contract for typed human input: `clojure.spec` over the NORMALIZED form of a request, plus the closed vocabulary that form is built from. Two layers, one contract. `com.blockether.vis.internal.human-input` PARSES — an extension (or a Python object arriving as JSON) writes `is_required` or `:is-required`, `"otp"` or `:otp`, and the normalizer turns whatever came in into one internal shape, naming the key its author has to fix when it cannot. This namespace DECLARES that shape: every map is CLOSED, `:name` and `:id` are the same identity, a `:select` really carries options, an `:otp` really fits its boxes, and a `:default` really is a value of the field's own type. Both are needed. A parser that only refuses bad INPUT still lets a bug INSIDE the engine hand a surface a field with no `:label`, or hand a blocked extension an answer with no `:request-id` — and that failure then surfaces as a broken dialog three namespaces away from its cause. The specs are checked once per request and once per answer, never per keystroke, so the guard sits where it costs nothing. The functions here only EXPLAIN ([[field-error]], [[group-error]], [[request-error]], [[answer-error]] return nil or a one-line reason); the refusal itself stays in `human-input`, which owns the error envelope every surface already handles.
(answer-error fields answer)nil when answer is a legal answer to hand a blocked extension, else why it
is not.
fields are the fields of the request being answered: a SUBMITTED answer is
checked against them too, so a value can only reach an extension for a field
that asked for it and only inside that field's own domain. nil fields means
the request settled while this answer was in flight — nobody is left to read
it, so its own shape is all there is to check.
nil when `answer` is a legal answer to hand a blocked extension, else why it is not. `fields` are the fields of the request being answered: a SUBMITTED answer is checked against them too, so a value can only reach an extension for a field that asked for it and only inside that field's own domain. nil `fields` means the request settled while this answer was in flight — nobody is left to read it, so its own shape is all there is to check.
Every key a field answered from :options may carry.
Every key a field answered from `:options` may carry.
Field types answered by picking from :options — exclusive, then inclusive.
Field types answered by picking from `:options` — exclusive, then inclusive.
(decor-error decor)nil when decor is a legal normalized decoration, else why it is not. The
three node contracts are checked apart, so each refuses the other two: a
heading carrying a :default is a spec that meant to ask something.
nil when `decor` is a legal normalized decoration, else why it is not. The three node contracts are checked apart, so each refuses the other two: a heading carrying a `:default` is a spec that meant to ask something.
Every key a decoration may carry: its own type and the words it paints. A node nobody can answer has nothing else to say.
Every key a decoration may carry: its own type and the words it paints. A node nobody can answer has nothing else to say.
Wire type name -> internal type of a DECORATION: a node that asks nothing and arranges nothing, and is on the form purely to be READ.
A heading breaks a long form into sections and a paragraph explains one.
Neither is in field-types and neither is a group-type: a decoration
holds no answer — no default, no options, no rules, no key in the answer map,
no focus stop — and owns no children either. It has no :name at all, which
is the point: there is no identity, so two headings reading the same words are
two decorations rather than a name collision.
Wire type name -> internal type of a DECORATION: a node that asks nothing and arranges nothing, and is on the form purely to be READ. A `heading` breaks a long form into sections and a `paragraph` explains one. Neither is in [[field-types]] and neither is a [[group-type]]: a decoration holds no answer — no default, no options, no rules, no key in the answer map, no focus stop — and owns no children either. It has no `:name` at all, which is the point: there is no identity, so two headings reading the same words are two decorations rather than a name collision.
(decoration? {:keys [type]})True when this normalized node is a decor-types decoration — ink on the
form rather than a question. Every surface asks this before it looks for a
value, and the answer contract never sees such a node at all.
True when this normalized node is a [[decor-types]] decoration — ink on the form rather than a question. Every surface asks this before it looks for a value, and the answer contract never sees such a node at all.
Keys the ENGINE stamps on a normalized node, never written in a spec.
:is-secret follows from the type, so a caller offering it is refused.
Keys the ENGINE stamps on a normalized node, never written in a spec. `:is-secret` follows from the type, so a caller offering it is refused.
(field-error field)nil when field is a legal normalized FIELD — a leaf holding one answer —
else why it is not. A layout group is not a field (group-error) and
neither is a decoration (decor-error).
nil when `field` is a legal normalized FIELD — a leaf holding one answer — else why it is not. A layout group is not a field ([[group-error]]) and neither is a decoration ([[decor-error]]).
Every key a field spec may be WRITTEN with: the union of the per-type sets, less what the engine derives. The parser accepts exactly this vocabulary in its snake_case spelling, so there is no second table of keys to keep in step.
Every key a field spec may be WRITTEN with: the union of the per-type sets, less what the engine derives. The parser accepts exactly this vocabulary in its snake_case spelling, so there is no second table of keys to keep in step.
Wire type name -> internal field type. A CLOSED set: an unknown name is refused with these listed, never minted into a keyword the surfaces cannot paint.
Only a type that holds an ANSWER belongs here. A layout group does not —
see group-type.
Wire type name -> internal field type. A CLOSED set: an unknown name is refused with these listed, never minted into a keyword the surfaces cannot paint. Only a type that holds an ANSWER belongs here. A layout `group` does not — see [[group-type]].
Wire direction name -> internal group direction.
Wire direction name -> internal group direction.
(group-error group)nil when group is a legal normalized layout group, else why it is not. The
children it arranges are checked as the nodes they are, so one call covers
the whole subtree.
nil when `group` is a legal normalized layout group, else why it is not. The children it arranges are checked as the nodes they are, so one call covers the whole subtree.
Every key a layout group may carry. A node that holds no answer has no key that describes one.
Every key a layout group may carry. A node that holds no answer has no key that describes one.
The type of a layout GROUP: the one node of a request's field tree that holds
no answer, and deliberately NOT a member of field-types.
A group is the control flow ABOVE the fields, not a ninth kind of field. It
owns the children it arranges and the direction they run in, and nothing that
describes a value — no default, no options, no rules, no key in the answer
map. Listing it beside :otp said the opposite: every value path had to
carry a branch for a node that can never take one, and a field spec would
happily accept a group.
The type of a layout GROUP: the one node of a request's field tree that holds no answer, and deliberately NOT a member of [[field-types]]. A group is the control flow ABOVE the fields, not a ninth kind of field. It owns the children it arranges and the direction they run in, and nothing that describes a value — no default, no options, no rules, no key in the answer map. Listing it beside `:otp` said the opposite: every value path had to carry a branch for a node that can never take one, and a field spec would happily accept a group.
How a wire spec asks for a group-type node.
How a wire spec asks for a [[group-type]] node.
The keys only a group has. A field carrying one meant to group and forgot to say so, which is worth its own message rather than an unknown-key refusal.
The keys only a group has. A field carrying one meant to group and forgot to say so, which is worth its own message rather than an unknown-key refusal.
Every key one :options entry may carry.
Every key one `:options` entry may carry.
How many boxes a one-time code gets by default, and the most it may ask for: past a dozen the boxes no longer fit a narrow dialog.
How many boxes a one-time code gets by default, and the most it may ask for: past a dozen the boxes no longer fit a narrow dialog.
The track a :range field falls back to. A slider with no bounds is a
PERCENTAGE — the one scale every operator already reads without being told
what the numbers mean — and every surface fills the same three numbers in, so
a hand-made request view draws the same knob a normalized one does.
The track a `:range` field falls back to. A slider with no bounds is a PERCENTAGE — the one scale every operator already reads without being told what the numbers mean — and every surface fills the same three numbers in, so a hand-made request view draws the same knob a normalized one does.
Every key a field answered on a track may carry.
Every key a field answered on a track may carry.
(request-error request)nil when request is a legal normalized request, else why it is not.
nil when `request` is a legal normalized request, else why it is not.
Keys the ENGINE stamps on a pending REQUEST, so every channel sees them on the
projected view although no spec may write one: the registry's arrival time.
Same category as derived-keys one level up — request-keys deliberately
refuses them on the way in, so a reader rebuilding a view that crossed a
process boundary lifts them across instead of re-parsing them.
Keys the ENGINE stamps on a pending REQUEST, so every channel sees them on the projected view although no spec may write one: the registry's arrival time. Same category as [[derived-keys]] one level up — `request-keys` deliberately refuses them on the way in, so a reader rebuilding a view that crossed a process boundary lifts them across instead of re-parsing them.
What a submitted secret — a :password or an :otp — becomes before its
answer leaves the engine. The plaintext stays in a process-local vault; this
prefix is the whole difference between an answer that is harmless in a log
and a leaked credential, so the answer contract is declared in terms of it.
What a submitted secret — a `:password` or an `:otp` — becomes before its answer leaves the engine. The plaintext stays in a process-local vault; this prefix is the whole difference between an answer that is harmless in a log and a leaked credential, so the answer contract is declared in terms of it.
(secret-handle? value)True when value is an opaque handle minted for a secret-types field.
True when `value` is an opaque handle minted for a `secret-types` field.
Field types whose value must never reach a log, an event or a transcript. A one-time code is as much a credential as a password — it opens the account once — so both answer with a vault handle instead of what the human typed.
Field types whose value must never reach a log, an event or a transcript. A one-time code is as much a credential as a password — it opens the account once — so both answer with a vault handle instead of what the human typed.
Every key a typed field may carry.
Every key a typed field may carry.
Field types whose answer is typed text.
Field types whose answer is typed text.
Every key an answerable field may carry, whatever its type.
Every key an answerable field may carry, whatever its type.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |