Toolchain: CLI, library, MCP, and canonical JSON
The code in this chapter uses the earlier surface of the language — with
категория,объект,утилита. Today’s compiler still reads those words but does not treat such a file as a program: a file holding only utilities is rejected byflang check. The reasoning in the chapter holds; the syntax is carried over via the table in «Older models».
This chapter is about tools around the language, not about syntax: how to install FTS, what commands to check the model with and what exactly is sent between processes, if it runs not only Node.js, but also React in the browser and an agent via MCP.
Minimal example
категория «Продажи»
объект Покупка
сумма является деньгами
«постоянный клиент» является признаком
утилита «Рассчитать скидку»
принимает Покупка
возвращает деньги
начинает с 0
правило «Большая покупка»
если сумма не меньше 10000
и сумма не больше 100000
то добавить 10 процентов от поля сумма
правило «Постоянный клиент»
если «постоянный клиент» равен да
и сумма больше 0
и сумма не больше 100000
то добавить 5 процентов от поля сумма
правило «Очень крупная покупка»
если сумма больше 100000
то добавить 15000
свойство «Скидка ограничена»
результат не больше 15000
пример «Обычная покупка»
дано сумма равна 5000
дано «постоянный клиент» равен нет
ожидается результат равен 0
пример «Постоянный клиент на пять тысяч»
дано сумма равна 5000
дано «постоянный клиент» равен да
ожидается результат равен 250
пример «Большая покупка постоянного клиента»
дано сумма равна 20000
дано «постоянный клиент» равен да
ожидается результат равен 3000
пример «Покупка на потолок скидки»
дано сумма равна 100000
дано «постоянный клиент» равен да
ожидается результат равен 15000
пример «Очень крупная покупка»
дано сумма равна 200000
дано «постоянный клиент» равен нет
ожидается результат равен 15000
Further in this chapter we will look at what this compiles into and with what commands this is checked.
What the compiler does
CLI, MCP and the library do not duplicate semantics — they call the same
compile/validate/testUtilities/generateTypeScript/prove/certify.
cli.ts and mcp.ts do not contain language logic: they read the source or
canonical JSON, call the core function and print the result. Therefore
the behavior of fts check model.fts in the terminal and fts_check in the agent matches
literally — this is one code, not two similar ones.
Installation
The language is open — github.com/digitable-lol/flang — and installs with a single command:
$ brew install digitable-lol/tap/flang
$ flang --version
flang 0.7.3
Building from a clone works too, and all it needs is a C compiler — no Node, no Python:
git clone https://github.com/digitable-lol/flang && cd flang
make -C bootstrap -j8
./bootstrap/flang check flang/stdlib/lists.flang
Both roads produce the same binary, built from C99. It has twelve commands, nine
print targets and four equal extensions for the input file (.flang, .fp,
.фп, .фланг).
What is no longer there: the old npm package
The toolchain this chapter was written against shipped through npm and went by
six commands — fts, ftsc, ftsvm, ftspec, fts-mcp, flang-lsp. The
channel is closed, the package is withdrawn, and that implementation stayed at
the v0.4.7 tag. Two commands moved across under new names: the AI assistant
service is now flang --mcp-mode, and the language server is
flang lsp --stdio.
The trap worth knowing before the first run
Today’s compiler accepts a file written in the earlier surface and answers green, having checked nothing at all:
$ flang check discount.fts
модуль «Продажи»: функций 0, из них с доказанным завершением 0; типов 1
discount.fts: проверено — разбор, типы, завершаемость, ядро и примеры; замечаний нет
Read that answer by its numbers, not by its last line. The category became a
module, the object Покупка became a type, and the utility did not
become a function: функций 0. There was nothing to check — hence “no remarks”
and exit code 0. Measured across the 35 models of this site: 28 answer the
same way.
What is actually not proved in the file is spelled out by flang check --proof:
“no declared functions”, “no declared laws”. Hence the practical rule: on files
of the earlier surface, trust the zeros on the first line rather than the word
“проверено” on the second.
How to carry a model over into today’s syntax — as a table, in the chapter «Older models».
One pipeline, three overlays
Library API fits a Node.js application and works the same in the browser (without Node certificate module):
import { compile, validate, testUtilities } from '../../../static/js/vendor/fts/browser.js'
const document = compile(source)
const report = validate(document)
if (!report.valid) throw new Error(JSON.stringify(report.diagnostics))
CLI fits CI and any language that can run a process. Full list of commands:
fts compile model.fts # source -> канонический JSON
fts check model.fts --pretty # компиляция + validate
fts test model.fts --pretty # выполнить примеры утилит
fts run model.fts --utility "Рассчитать скидку" --input input.json
fts generate model.fts --out generated # TypeScript + node:test
fts prove model.fts --context ctx.json # символьный вывод теоремы
fts certify model.fts --context ctx.json --pretty > proof.json
fts verify model.fts --context ctx.json --certificate proof.json
fts visualize model.fts --mode proof
fts pipeline model.fts --context ctx.json --mode all
fts mcp # поднять MCP-сервер по stdio
A successful team prints JSON to stdout; check and test on failure print
diagnostics to stderr and exit with a non-zero code.
MCP fits the agent. The server declares itself as fts (Formal Type Surface,
0.3.0) and publishes read-only tools: fts_compile, fts_check,
fts_test, fts_generate, fts_execute, fts_prove, fts_visualize,
fts_certify, fts_verify, fts_pipeline. The agent does not gain access to
the file system through these tools: source or already compiled
document, as well as context and certificate are passed as explicit JSON-
arguments of the call, not a file path.
Canonical JSON
All surfaces converge to a single FtsDocument. This is how the
fts compile of the minimal example for this chapter looks in reality (shortened — the full list
rules/examples is longer):
{
"category": "Продажи",
"structures": [
{
"name": "Покупка",
"fields": [
{ "name": "сумма", "type": "Деньги" },
{ "name": "постоянный клиент", "type": "Признак" }
]
}
],
"functors": [],
"proposition": null,
"ts_compat": {},
"utilities": [
{
"name": "Рассчитать скидку",
"input": "Покупка",
"output": "Деньги",
"initial": 0,
"rules": [
{
"name": "Большая покупка",
"when": [
{ "field": "сумма", "operator": "gte", "value": { "kind": "value", "value": 10000 } },
{ "field": "сумма", "operator": "lte", "value": { "kind": "value", "value": 100000 } }
],
"action": { "kind": "add", "value": { "kind": "percent", "percent": 10, "field": "сумма" } }
}
],
"properties": [
{
"name": "Скидка ограничена",
"operator": "lte",
"value": { "kind": "value", "value": 15000 }
}
],
"examples": [
{ "name": "Обычная покупка", "input": { "сумма": 5000, "постоянный клиент": false }, "expected": 0 }
]
}
]
}
This is the main integration contract. React is not required to understand Russian
keywords: it receives structures and builds a form. Python is not required
to embed a TypeScript parser: it reads JSON from stdout CLI. The agent does not receive
the right to read an arbitrary file: it passes the source as a JSON argument to MCP.
The functors field is an internal name of a section for морфизм; this is a name inherited
from the first version of the wire format fts/1, not a hint about functors in category
theory in user syntax. ts_compat is reserved for explicit TypeScript compatibility hints for fields — in the models of this course
it is usually empty.
JSON-first discipline
Do not parse pretty text and do not determine success by the presence of the word ok —
orient yourself by the exit code and the JSON field valid:
if fts test policy.fts > result.json; then
node publish-report.mjs result.json
else
echo "FTS policy failed" >&2
exit 1
fi
Practice in the sandbox
Read the JSON response check. Find the field valid in it and make sure that
diagnostics is an empty array.
Call the «Calculate Delivery» utility with weight 20 and distance 100. Calculate the expected number manually according to the rules and compare it with the result.
Open the generated TypeScript. Find in it the test corresponding to the
example “Regular customer for five thousand”, and compare the numbers with the original
.fts-model.
Common Fallacies
FTS_NO_UTILITY_EXAMPLES — fts test is triggered on a document where none
of the utilities have a single пример. The model may be syntactically valid
and even pass fts check, but test will refuse to confirm anything:
the diagnostics literally say «utilities do not contain examples». Fix — add at least
one пример with дано and ожидается.
FTS_UTILITY_INPUT_TYPE — fts run or executeUtility received
JSON input where the field value does not match the declared structure type.
If a sum is passed as a string "20000" instead of a number, the compiler will respond
поле «сумма» не соответствует типу «Деньги». This is a deliberate choice by the core:
the HTTP service from examples/spec/discount-api returns such an error to the client
as 400, not as 500, because the cause lies in the request data, not in
the server logic. Fix — cast types at the boundary before calling the utility.
Checklist
- I know which CLI command to use for verification, for utility tests, and for running one computation.
- I can explain the difference between a library API, a CLI process, and an MCP- tool in one phrase: one compiler, three interfaces.
- I find structures, rules, and examples in canonical JSON without reading
the source
.fts. - I check the CLI result by exit code and the
validfield, not by text.