sym-service — 多 runtime 符号计算网关
一个 worker 干两件事:出静态文档站,以及给两个不同 runtime / 架构的符号计算后端做统一入口。
┌─ SYMBOLICA_URL → Unikraft Cloud(Rust + Symbolica,x86_64)
客户端 → WinterJS (WASIX) ──────┤
dist/worker.js └─ SYMPY_URL ────→ Wasmer Edge(CPython + SymPy,wasm32-wasip1)
├─ 静态页(Astro 构建产物,内联成资源表)
└─ POST /v1/evaluate · GET /v1/capabilities · GET /healthz · GET /
网关不发明协议。 两个上游早已是同一套形状(单端点 + op 闭集 + 五段信封 +
result.values[] + verification 三态 + 12 个共用错误码),本项目只补它们做不了的四件事:
能力并集、按 op 路由、在信封里补一格 provenance(谁算的、跑在哪个 runtime / 架构)、
把两端的 latex 印刷统一到 sympy 的打印机(\arctan{\left(x \right)}、运算符两侧空格、无裸 atan)。
契约按「输入 WL,输出看 latex」用:wl 是引擎自己的书写,跨引擎不可比。
调用方把 SYMPY_URL 换成网关地址,程序一个字不用改。理由、差异清单、判定顺序见
docs/gateway-schema.md。
同一份代码在 Bun 里调试,打包成单文件 worker 后交给 WinterJS。依赖用 Bun 管,
.cnb.yml 在进开发环境前把工具链和依赖装好。
快速开始
bun install
bun run dev # http://127.0.0.1:8787,改 src/ 自动重打并重启(改 src/site 会先 astro build)
bun test # 257 条 / 19 个文件;其中 78 条联网(sympy 56 + symbolica 22),未配上游地址时跳过
# 静态页那 12 条依赖 dist/site:构建过才真跑,否则 skip(顺序见 docs/test-coverage.md §5)
bun run build # rm -rf dist/site → astro build → 打包成 dist/worker.js(约 1.3 MB)
bun run wasmer # build 后用真 runtime 跑:wasmer/winterjs@1.2.0,端口 8080
# 网关的三条接口(未配上游时:能力表为空、evaluate 一律 422 op_unroutable)
curl -s localhost:8787/v1/capabilities
curl -s localhost:8787/healthz
curl -s -XPOST localhost:8787/v1/evaluate \
-H 'content-type: application/json' \
-d '{"op":"integrate","expr":"x^2"}'
cp .env.example .env # 填 SITE_URL(构建期出 sitemap 用)
bun run deploy # 读 .env → build → wasmer deploy(需 WASMER_TOKEN)
变量
三地址即可跑通:SITE_URL(本站域名,构建期)+ SYMBOLICA_URL / SYMPY_URL(两个上游,
期)。上游地址只从环境读并在构建期注入产物——域名可能带 token,不进仓库;缺哪个,哪个后端的 op 就回
422 op_unroutable(合法状态,不是错误配置)。
cp .env.example .env # 模板里的注释就是说明书
规矩、优先级、排错见 docs/env.md。可选开关只有 WASMER_APP_DOMAIN(SITE_URL
兜底)、BASE_PATH(子路径)、SYM_VERSION(调版本回显)、WASMER_TOKEN(部署凭证,云原生
开发已注入)。BASE_PATH 只管静态资源前缀;API 路径不变,/sym/healthz 仍需部署层改写。
目录
src/app.js 装配:API 优先(闭集),其余查静态表
src/gateway.js 网关四条路由 + 判定顺序 + 上游故障四态
src/envelope.js 统一错误信封(两端的逐字拷贝)+ request_id
src/schema/contract.js 协议真源:字段 / 限额 / 码表 / 上游表 / 路由判据
src/schema/request.js 请求闭集解析(未知键 / 缺字段 / 值域,各自给 detail)
src/upstream/client.js 转发:35 s 预算、连不上 vs 答了、透传不改写
src/upstream/capabilities.js 能力并集:可达性与能力分开报,冲突留痕
src/version.js 包版本(构建期 define,跑源码时读 package.json)
src/static.js 静态资源表:查表 + 本地读盘兜底
src/worker.js WinterCG 入口,注册 fetch 事件
src/winterjs-compat.js WinterJS 的 Response 兼容补丁(关键,见 docs/winterjs.md)
src/site/content/docs/ Starlight 页面(Markdown/MDX,构建期出 HTML)
src/site/content.config.ts docs 集合(docsLoader + docsSchema)
astro.config.mjs Astro/Starlight 配置(srcDir/outDir/site/base/侧边栏)
scripts/build.mjs loadEnv → cleanSite → astro build → 收集产物 → Bun.build 单文件 worker
scripts/env.mjs 读 .env(空值视为未设置;给 astro 子进程传 --env-file)
scripts/deploy.mjs 读 .env → build → wasmer deploy,域名只需配一次
scripts/astro.mjs cleanSite(rm -rf dist/site)+ 产物收集(base64 内联)
scripts/serve.mjs Bun.serve 模拟 worker 环境
scripts/dev.mjs fs.watch + 重启,site 变动先重建静态页
scripts/install-bun.sh 装 bun(钉 1.4.4,走 GitHub release + sha256)
scripts/install-wasmer.sh装 wasmer cli(钉 7.4.0,原因见 docs/toolchain.md)
test/ bun test
wasmer.toml Wasmer 包清单
app.yaml Wasmer Edge 应用清单
.cnb.yml 云原生开发环境
云原生开发
.cnb.yml 只有一条 vscode 流水线:分支页点「云原生开发」时启动,stages 在进 IDE 之前跑完。
| 项 | 值 |
|---|---|
| 触发 | vscode(页面「云原生开发」按钮) |
| 环境 | 默认开发环境 cnbcool/default-dev-env(不声明 docker 即默认镜像) |
| 服务 | vscode(单容器,WebIDE + VSCode Remote-SSH) |
| 资源 | runner.cpus: 2(2 核 / 4 GiB) |
| 密钥 | imports 引用 Tomori0Nao/TOKENS 的 wasmer/env.dev.yml(注入 WASMER_TOKEN) |
stages[0] toolchain jobs 对象 → 两路并行
├─ install bun scripts/install-bun.sh
└─ install wasmer WASMER_VERSION=7.4.0 scripts/install-wasmer.sh
stages[1] deps bun install --frozen-lockfile
stages[2] verify wasmer --version + bun test + bun run build(allowFailure)
三个语法点,写错就静默失效:
jobs数组 = 串行,jobs对象 = 并行;stages之间永远串行。bun install不能和「装 bun」并行——它用的就是刚装到/usr/local/bin的 bun,不是镜像自带的。- 每个 stage 是新 shell,
/etc/profile.d不保证被读到,所以deps/verify里显式补PATH。
imports 读的是文件:必须写到 wasmer/env.dev.yml 这一级且用 /-/blob/。/-/tree/ 是目录页、
/-/raw/ 会被判 Invalid file resource。密钥仓库默认只允许负责人/管理员触发的流水线引用,
放开要写 allow_slugs + allow_events(含 vscode,只写 push 读不到)。
版本
| 组件 | 版本 | 说明 |
|---|---|---|
| bun | 1.4.4 | 见 docs/toolchain.md |
| hono | 4.13.14 | 唯一运行时依赖 |
| astro | 7.4.4 | 只做构建期静态产出,不进 worker |
| @astrojs/starlight | 0.42.8 | 站点主题,与 rs-site-tools 同款;0.41+ 要 Astro 7 |
| @astrojs/sitemap | 3.7.4 | 配了 SITE_URL 才启用;跟 astro 7 同代(都用 zod/v4) |
| wasmer cli | 7.4.0 | 7.4.1+ 要 glibc 2.38,默认镜像只有 2.36 |
| wasmer/winterjs | 1.2.0 | 模块名是 winterjs,不是子命令 serve |
接口
| 方法 + 路径 | 做什么 |
|---|---|
POST /v1/evaluate | 求解。op 决定去哪:integrate → symbolica,其余 25 个 → sympy |
GET /v1/capabilities | 两边 ops 的并集(现拉现并),每个 op 标出 upstream |
GET /healthz | 网关自身 + 每个上游的 reachable / ready / engine / version;?deep=1 才真打上游 |
GET / | 服务信息;静态站建过就出首页(平台的就绪探测打这里) |
错误统一 {request_id, error:{code, message, detail}}:12 个共用码 + 上游的
503 engine_unavailable / 504 timeout(原样透传)+ 网关自产的 422 op_unroutable。
文档
| 文件 | 内容 |
|---|---|
| docs/gateway-schema.md | 两端协议对账、网关加的四样(含 latex 规范化)与刻意不加的四样、判定顺序、故障四态 |
| docs/astro-integration.md | 方案对比、Starlight 主题、构建两段式、三处硬细节、路由 |
| docs/cnb-pipeline.md | .cnb.yml 逐段解释、并行/顺序的判据、密钥接入 |
| docs/winterjs.md | c.json() 在 WinterJS 上 500 的根因、补丁、打包 target |
| docs/toolchain.md | bun / wasmer 的版本钉法与 glibc 分界、脚本做了什么 |
| docs/deploy.md | wasmer.toml 三处不能省、本地跑真 runtime、部署流程 |
| docs/env.md | 三个地址(本站 + 两上游)、.env 的优先级与空值语义、谁在读它 |
| docs/verification.md | 一次部署 + 经网关验证的记录、修掉的五个运行时差异、冷启动与已知限制 |
| docs/test-coverage.md | 257 条用例的覆盖矩阵、公式语料格式(每 op 简单+复杂各一条)、归一化比对规则、剩余缺口 |
| docs/site-audit.md | 站点内容核对记录:4 处与线上不符、示例页形态、HEAD 500、构建告警 |
本站(bun run wasmer 后访问 /)只写给调用方看的东西,不写实现内幕:
| 路径 | 内容 |
|---|---|
/ | 一句话说清是什么 + 三条必知规矩 |
/guide/start | 一次 curl、换语言怎么调、HTTP 状态分三类 |
/api | 端点、五个请求键、五段信封、26 个 op 两组 |
/api/response | 逐字段,以及「200 但没有答案」的三种情况 |
/api/errors | 12 共用码 + 3 特殊情况码,detail 结构,上游故障四态 |
/api/ops | 26 个算子按组列全,args 含义 |
/examples | 每个算子 simple + complex 各一条可 curl 的请求与期望值 |
docs/ 是仓库维护视角(yaml 逐段、版本钉法、排错记录、测试口径),不上站点。
两侧讲同一件事时以 docs/ 为准,站点在它上面精简成调用方需要的最小面。
用 VSCode 连接
- 本地装 VSCode + Remote-SSH 插件
- 分支页点「云原生开发」启动环境
- 点页面上的 VSCode 入口按提示连接
窗口被覆盖时把 Window › New Window › Open Folders in New Window 设成 on。
想换镜像才需要写 docker.image;写了未装 code-server 的镜像会退化成双容器模式。
开发环境默认 10 分钟没心跳回收,可给 services 的 vscode 加 options.keepAliveTimeout。