TANGEUL — Rules Written in Markdown, Audited by Humans 图片:AI 生成

toulmin 计算契约。规则是 Go 函数,例外被声明为一张击败图(defeat graph),而 h-Categoriser 把这张图转成一个裁决。问题在于,真正需要审核这份契约的人往往读不懂 Go。

一位审核放贷政策的税务师。一位检查访问控制规则的小微企业主。一位复核转账补偿逻辑的银行柜员。他们中没有一个人能看着 func(ctx Context, specs Specs) (bool, any) 说出"对,这是正确的"。无论引擎的数学多么严密,只要读者读不懂代码,这场审核就是一场表演。

AI 写它,人类读它

TANGL(Toulmin Amgoud Nute Graph Language)弥合了这道鸿沟。你书写 markdown 句子,而那些句子就是可执行的代码。

## tangl:Cases
- in case of `can access`
  - `user` is required
  - `authenticate` is a general rule
  - `block ip` is a counter rule using `policy`.`IsIPBlocked` with `blocklist`
  - don't `authenticate` when `block ip`
  - do `policy`.`Allow` when `authenticate`

这五行就是整张 toulmin 图 —— RuleCounterAttacks,以及执行边。作者无需去操心任何单独的编译步骤:tangl 工具链把这段 markdown 直接解析成 *ast.Document,validate/effects/gen 原封不动地在其上运行。作者是 AI,验证者是人 —— 这种不对称正是全部要点所在。AI 只需遵循一套严格的规范文法;人类只需读懂一句属于自己语言的话。

一套语义,多重表层

TANGL 的语义只定义一次,即在英语文法中:Evaluate/Run 对偶、once(tick 幂等)、undo(补偿)、级联时机、确定性执行顺序。任何本地化版本 —— 韩语,或接下来的任何语言 —— 都不重新定义这其中任何一项。它只是把叠加在同一套 AST 之上的关键字与语序表换掉。一句韩语句子与一句英语句子 1:1 对应,二者都解析为完全相同的 AST 节点。

这不是为了图方便而外挂上去的翻译层 —— 它是承重结构。TANGL 在韩国的真实读者是非开发者审核员:소상공인、세무사。像 don't X when Yis a general rule 这样的英语关键字,无法让一位非开发者确认"对,这读起来是对的"。如果读者是韩国人,那么表层必须是韩语,审核才能真正起作用 —— 反过来对英语读者也对称地成立。

构建韩语版本浮现出一件仅凭英语文法看不出的事:韩语的 SOV 语序不是翻译负担,而是更契合条件规则。`Y`면 `X` 실행한다(大致是"当 Y 时,执行 X")在韩语中读起来比 do X when Y 更自然 —— 条件在前、谓语在后,本就是这门语言的构造方式。韩语的助词策略把这一点推得更远:助词(은/는/이/가/을/를 …)是词法分析器丢弃的可选记号,但 AI 所写的规范形式始终照样带上它们 —— 是为了人类读者,而非解析器。既然 AI 是唯一的作者,解析器就永远不必接受自由语序或口语变体;它只需解析一种规范形式。正是这种不对称,让一个并不平凡的自然语言表层变得可处理。

七个区块

Subject → See → Definitions → Rules → Cases → Provides → Internal
区块作用是否必需
Subject声明本文档关乎什么必需
See引用外部包符号可选
Definitions术语与结构体可选
Rules内联条件谓词可选
Cases判定 + 执行(图本身)必需
Provides端点可选
Internaltick/事件驱动的内部工作可选

每种构造只在自己的区块内有效 —— 这是任何本地化版本原封不动继承的同一套作用域纪律。语句读起来是自报其类别的完整句子:is a general ruledon't ... whendo ... once whenundo ... whenrun ... when

示例:一台咖啡机器人

下面展示 TANGL 的执行语义 —— Evaluate/Run 对偶、once、级联 —— 如何呈现为普通的 markdown 句子。

## tangl:Cases
- in case of `can place cup`
  - `order received` is a general rule using `sensor`.`isOrdered`
  - `no cup` is a counter rule using `sensor`.`noCupAvailable`
  - don't `order received` when `no cup`
  - do `arm`.`placeCup` once when `order received`
  - run `can pour water` when `order received`

- in case of `can pour water`
  - `cup placed` is a general rule using `sensor`.`cupIsPlaced`
  - do `arm`.`pourWater` once when `cup placed`
  - run `can brew espresso` when `cup placed`

## tangl:Provides
- provides `make coffee`
  - run `can place cup`

## tangl:Internal
- every 1s until `can serve coffee`
  - run `can place cup`

一位审核员这样读它:“一旦可以放杯 —— 如果订单已收到,就放杯一次并转入倒水。如果没有杯子,订单作废。要做咖啡,从放杯开始,并每秒重试,直到可以上咖啡为止。”

没有 once,只要订单保持活跃,机械臂就会在每个 tick 放一个新杯子 —— 正是这个词,成为一次缓慢的传感器更新不至于变成物理上不安全的重复运行的全部原因。审核员从这一句话就能确认重复运行的安全性,而无需读一行 Go 守卫语句。

示例:补偿 —— 世界即便在失败时也保持完整

undo 与拒绝并不相同。拒绝是图的一个正常分支;undo 则是对一次半途夭折的执行所作的补偿。

- do `bank`.`withdraw` once when `balance sufficient`
- undo `bank`.`refund` when `balance sufficient`
- run `can deposit` when `balance sufficient`

“如果余额充足,就扣款一次;如果下游任何环节失败,就通过退款回滚;然后继续存款。“失败时,顺序是确定性的:

1. withdraw succeeds        → refund is armed on the compensation stack
2. deposit errors           → the Run aborts immediately
3. stack unwinds LIFO       → refund fires, the money comes back
4. the original error is reported — the transfer failed, and the world looks like it never happened

如果 refund 本身也失败了,剩下的补偿就停下,整笔事务升级到人工复核关卡(REVIEW)—— 系统自行判定:一笔执行到一半的转账,不是可以自动糊弄过去的事。

文本不再是真理之源之处 —— TANGEUL

以上的一切都是 TANGL:直接被解析并执行的 markdown 文本。但文本作为执行输入是一种便利,而非必然。一旦一门语言有了两重表层 —— 英语与韩语 —— “哪一个是规范的?“这个问题就出现了。眼下的答案是"英语,按惯例而定”。那是一个决定,而非结构性的保证。

TANGEUL 消除了这个问题,而且它已经作为 pkg/tangeul 二进制单一真理之源随 tangl 工具链一同发布。规范的执行制品是一条二进制图流.tangeul),check/ast/effects/gen 全都直接在它之上运行;英语和韩语的 markdown 文件是表层编解码器,吸收进这同一张图、又从中渲染回来。

 EN .md ──[EN codec]──┐                        ┌──[mode 1]── verbatim byte-identical round trip
                      ├──> .tangeul (SSOT) ────┤
 KO .md ──[KO codec]──┘                        └──[mode 2]── normalized EN/KO rendering
  • Mode 1(逐字):精确重放所写入的字节,因此解码输出与原始 .md 逐字节相同 —— 由 CLI 往返测试检验
  • Mode 2(重新生成)decode --locale en|ko 通过目标本地化的编解码器重新渲染每条语句,逐字重放散文跨度。一份以韩语撰写的文档可以为英语审核员渲染,反之亦然。

一份文档的撰写表层被固定为单一本地化(在一个文件里混用英语和韩语区块会被拒绝),但 .tangeul 流本身并不知道是哪种本地化写下了它。它能按需被渲染成任一种语言,恰恰是因为规范制品是一张图,而非一段文字。

所有位级知识都被隔离在两个包里 —— word(16 位字、帧头)和 codebook(仅追加的编码值)—— 于是它们之上的各层(包、流、文档容器)无论编码如何演进都保持稳定:

pkg/tangeul/
├── word/       // 16-bit words, frame headers — bit-level knowledge stops here
├── codebook/   // NodeType/PropCode/Control codes, append-only
├── packet/     // Node/Edge/Control/Text packets
├── stream/     // Doc ↔ .tangeul serialization
└── doc/        // Doc container + VerifyTiling + the surface Codec interface

VerifyTiling 是完整性关卡:所记录的块必须精确铺满源文本,它在每次加载和每次编码器输出时都会运行。对等性测试断言:从 .tangeul 流加载得到的 AST 等于解析器从原始 .md 生成的 AST(至多相差行号)。改用二进制并不是要改变代码所做的事 —— 图,以及由此生成的输出,都是相同的;它是要在结构上消除同一文本的多份副本之间发生漂移的可能。

可审核的契约

toulmin 的结论是:裁决由公式计算,而非由人裁定。TANGL 又给这句话添了一行:这个计算可被一个从未写过该公式的人读懂。

  • 依据(一条规则)= 一句话,`n` is a ... rule
  • 反驳 = 一句话,don't `X` when `Y`
  • 根据(证据)= 对某个被引用包函数的一次调用
  • 裁决 = h-Categoriser 公式

而 TANGEUL 所做的工作,是在字节层面保证:无论这些句子用哪种语言书写,它们都指向同一张图。写规则的人和审核规则的人,从此不必再共用一种语言。

MIT License. github.com/park-jun-woo/toulmin —— pkg/tanglpkg/tangeulcmd/tangl

参考文献

变更历史

  • 2026-07-03: First edition