Материал FTS toolchain: CLI, library, MCP, and canonical JSON
0%

FTS toolchain: CLI, library, MCP, and canonical JSON

FTS toolchain: CLI, library, MCP and canonical JSON

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 repository is open - github.com/digitable-lol/flang. The package is not yet published in npm, so the CLI is built from source code:

git clone git@github.com:digitable-lol/flang.git
cd fts
npm ci
npm test
npm run build

After publication in the registry, the package and CLI will be installed in the usual way:

npm install @digitable-lol/fts
npx fts check model.fts

FTS requires Node.js 20 or newer. The package has no runtime dependencies, access to the file system or network inside the core — the only I/O in CLI is reading the model file and optional writing of the generated code through --out.

Sixth team in the same package: flang

It is worth knowing in advance, because this changes the perception of the tool boundaries. In addition to fts, ftsc, ftsvm, ftspec and fts-mcp the package declares a sixth command — flang, a full language into which FTS has grown. And it accepts .fts directly, without conversion:

$ node flang/bin/flang.mjs check examples/utilities/discount.fts
{"valid":true,"module":"Продажи","functions":[{"name":"Рассчитать скидку","total":true}],
 "types":["Покупка"],"diagnostics":[]}

This answer is read as follows. The FTS category has become a module of the language, the object Покупка — a type, the utility — a function, and "total": true means that the language compiler has proven the computation termination: not “tests passed”, but “this function cannot loop indefinitely”. For the FTS utility, it could not be otherwise — loops and recursion are completely absent in the specification language — and therefore the compatibility promise is formulated stronger than “accepts a file”: any .fts model falls into the total class flang entirely.

One practical caveat: for flang to read .fts, the FTS core must be built — the bridge loads dist/src/index.js. Without npm run build you will get an honest ERR_MODULE_NOT_FOUND, not a mysterious parsing error.

The practical sense at this stage of the course is small: counting the model is simpler with the familiar fts run. The sense appears further, in the module «Integration with any language», — and there it is quite tangible.

Bridge between two worlds — module flang/src/compat.mjs, and it is designed so that it repeats the semantics of the core literally, not by meaning: the order of rules, short-circuiting of conditions inside a rule, the order of multipliers in percentages ((процент / 100) * поле, not the other way around — swapping changes the last bit of the mantissa) and checking properties after all rules. A separate decision is worth remembering, because it concerns the design of contracts in general: the code and the text of an violated property are sent to the program as data, not as knowledge, hardcoded in the engine. Otherwise, the coincidence of error codes between two implementations would depend on the implementation and would be random.

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 "@digitable-lol/fts"

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_EXAMPLESfts 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_TYPEfts 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/fts/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 valid field, not by text.

Cases in the catalog on this topic

Next: Russian and English

Нашли неточность? Выделите фрагмент текста — рядом появится жучок.

Нужен разбор именно вашей ситуации?

Статья описывает общий случай. Если у вас частный — можно разобрать его отдельно, платно. А если не хватает целого материала, предложите тему: её оплачивают вскладчину, и она выходит открытой для всех.

Доска запросов
Дальше