Executable specifications in flang Toolchain: CLI, library, MCP, and canonical JSON
0%

Toolchain: CLI, library, MCP, and canonical JSON

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 by flang 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_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/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 valid field, not by text.

Cases in the catalog on this topic

Next: Russian and English

Spotted a mistake? Select a fragment of the text — a bug icon will appear next to it.

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

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

Доска запросов
Next