Wymcp. Tool. Actions
(Wymcp v0.4.0)
View Source
Answers every question about the actions a tool module declares: validating
its Wymcp.Tool.actions/0 declaration wherever that declaration is
checked, and obtaining action schemas out of it wherever one is read.
Both halves are one validation layer. Action-schema validation runs the
same chain at each of its three moments, and runs once more — restricted to
the mandatory pair — at every runtime obtaining moment, which is why
validating and obtaining live together rather than in two modules: one
definition of carrying a key has to serve both sides, and here it does,
because check_mandatory_keys!/3 calls the chain's own two validators.
What a consumer may write into Wymcp.Tool.actions/0 is not stated here.
The format catalogue belongs with the behaviour a consumer implements, in
Wymcp.Tool's moduledoc under "Action schema format", and the three moments
this chain runs at are listed there under "When schemas are validated". This
module states the rule those moments enforce and holds the code enforcing
it.
The vocabulary itself also stays with the behaviour: Wymcp.Tool is the one
code home for the key list, read through Wymcp.Tool.action_schema_keys/0,
and the chain here reads it through that accessor at each use rather than
snapshotting it into an attribute of its own — one owner, read at run time,
so the two modules carry no compile-time dependency in this direction.
The callback-surface check is a different layer and stays in Wymcp.Tool,
under the callback-surface invariant there.
The action-schema invariant
Every key in the action-schema vocabulary is validated: a key outside
Wymcp.Tool.action_schema_keys/0 is rejected wherever validate!/1 runs
(unknown implies rejected), and every key inside it is checked by a
validator in that chain which rejects at least a wrong-type value (known
implies validated). That rule is the action-schema invariant.
Three clauses, each with its own enforcement:
- unknown implies rejected —
validate_known_keys!/3subtracts the vocabulary from an action schema's keys and raises on whatever is left, so a misspelling fails at one of the moments inWymcp.Tool's "When schemas are validated" instead of vanishing silently from help andtools/list. - known implies validated —
Wymcp.ActionSchemaInvariantTestderives one cell per key fromWymcp.Tool.action_schema_keys/0and asserts each one rejects a wrong-type value, so a key joining the vocabulary without a validator fails that test rather than shipping unchecked. - restated implies pinned — every other statement of the vocabulary is
derived from
Wymcp.Tool.action_schema_keys/0or compared against it by a cell in that same test:Wymcp.Tool.action_schema/0's key set, its mandatory/optional split againstWymcp.Tool.mandatory_action_schema_keys/0, andWymcp.Tool's "Action schema format" catalogue, which must carry a bullet for every key. The published type has no other enforcement — nothing writes@spec— so without that cell the contractWymcp.Toolpublishes and the chain this module runs can disagree while the build stays green.
The invariant is about coverage, not depth: a validator satisfies it by rejecting a wrong type. What a well-typed value may contain is that key's own contract — the framework validates no property values at all.
A read-side corollary follows: an action schema obtained for reading
carries its mandatory keys, and that is checked where it is obtained — not
where a field is read. fetch_schema!/3 and fetch_schemas!/1 are the
only paths by which a reader obtains an action schema, and each checks the
mandatory keys — the ones Wymcp.Tool.action_schema/0 writes bare — by
calling the same validators the wire-in chain runs, so one definition of
carrying a key serves both sides. A reader holding a schema may therefore
access those keys directly; a reader meeting a schema without them is
looking at a tool no validator ever saw, and obtaining raises rather than
let it publish an entry whose silence about :properties would read as a
claim that the action takes none.
Optional keys carry no such guarantee and are read with their default.
The check covers exactly what was obtained: the one-schema form checks one,
the all-schemas form checks all. That scope is the design, and over the wire
it reads as malformed-sibling isolation: a tool with one bad action
schema still serves its healthy actions through tools/call, which obtains
only the schema of the action it dispatches. The malformation surfaces on
every surface that renders a whole tool — both of Wymcp.Help's whole-tool
answers, its server index and its tool level, each of which obtains every
schema a tool declares — and at the bad action's own call, which raises and
is answered in the tool dialect. tools/list is on neither list: a
mount tool's definition is built at the registration moment, so there
the malformation aborts the mount module's compile instead
(Wymcp.Router), and a tool registered on a live session passed this same
validation at registration (Wymcp.Session.register_tool/2), so the
serve-time fallback that assembles its entry never sees an invalid tool —
its raise is defence in depth, not a live surface.
Help's action level is the one-schema surface, and is
consistent with dispatch: both hold the actions map as name material and
obtain only the schema they render.
Each entry point obtains once and passes the map it obtained to whatever it
calls — a path that obtained twice could observe two different maps. A
Wymcp.Tool.actions/0 whose result varies between calls violates the
contract Wymcp.Router states, so obtaining once is defence in depth for
the runtime readers rather than support for such a tool.
Summary
Functions
Obtain module's actions map as name and membership material, checking the
container's own shape and nothing else. Raises ArgumentError when
Wymcp.Tool.actions/0 does not return a map.
Obtain one action's schema out of an actions map already obtained through
fetch!/1, checking its mandatory keys. Raises ArgumentError naming the
tool, the action and the key when one is missing or malformed.
Obtain every action schema module declares, checking each one's mandatory
keys as it is obtained. Raises ArgumentError naming the tool, the action
and the key on the first schema that is missing one or carries it malformed.
Validate every action schema in module. Raises ArgumentError with a
descriptive message on the first malformed action.
Functions
Obtain module's actions map as name and membership material, checking the
container's own shape and nothing else. Raises ArgumentError when
Wymcp.Tool.actions/0 does not return a map.
No schema field may be read off what this returns — it is deliberately
not an obtaining accessor. The two callers that hold an actions map as
names — Wymcp.Tool.dispatch/3 and help's action level — cannot go through
fetch_schemas!/1 without reinstating the sibling contamination
fetch_schema!/3 exists to prevent, so they take the container here and
obtain the one schema they render there.
Obtain one action's schema out of an actions map already obtained through
fetch!/1, checking its mandatory keys. Raises ArgumentError naming the
tool, the action and the key when one is missing or malformed.
Takes the map rather than obtaining its own, so a caller that already holds
one as name material — Wymcp.Tool.dispatch/3, help's action level — reads
the schema it dispatches out of the very map it matched the name in, and a
tool whose Wymcp.Tool.actions/0 varies between calls cannot answer the
two questions from two different maps.
Obtain every action schema module declares, checking each one's mandatory
keys as it is obtained. Raises ArgumentError naming the tool, the action
and the key on the first schema that is missing one or carries it malformed.
The whole-tool form, for a caller that renders or publishes every action a
tool declares. A caller that needs one action's schema uses
fetch_schema!/3 instead, which leaves a malformed sibling to fail at its
own call rather than at this one.
Validate every action schema in module. Raises ArgumentError with a
descriptive message on the first malformed action.
Called at all three of the moments a tool's schemas are checked: by
__after_verify__/1 while a use Wymcp.Tool module itself compiles, by
Wymcp.Router.init/1 while a mount module compiles, and by
Wymcp.Session.register_tool/2 at runtime registration — so a
misconfigured tool fails as early as its own build, and no later than the
point it is wired in.