@calcit/procs 0.12.59 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/.yarn/install-state.gz +0 -0
  2. package/RFCs/08-04-strict-cirru-edn-decoding-rfc.md +342 -0
  3. package/RFCs/08-05-systematic-nil-reduction-rfc.md +174 -0
  4. package/RFCs/README.md +3 -1
  5. package/editing-history/202608050112-nil-reduction-foundation.md +33 -0
  6. package/editing-history/202608050943-pr-review-and-test-nil-reduction.md +29 -0
  7. package/editing-history/202608051010-explicit-unit-without-nil.md +30 -0
  8. package/editing-history/202608051720-nominal-option-api-migration.md +30 -0
  9. package/editing-history/202608051924-expand-option-collection-apis.md +31 -0
  10. package/editing-history/202608052014-js-nullish-boundary.md +29 -0
  11. package/editing-history/202608052255-nominal-reflection-and-destruct-apis.md +29 -0
  12. package/editing-history/202608060104-public-nil-contract-freeze.md +36 -0
  13. package/editing-history/202608060215-fix-hashmap-option-docs.md +4 -0
  14. package/editing-history/202608060220-release-0.13.0.md +5 -0
  15. package/editing-history/202608061340-release-0.13.1.md +6 -0
  16. package/history/202608040125-js-runtime-impl-identity.md +6 -0
  17. package/history/202608040241-strict-cirru-edn-decoding.md +27 -0
  18. package/history/202608040954-strict-edn-review-followups.md +16 -0
  19. package/history/202608041206-harden-js-impl-brand-check.md +5 -0
  20. package/history/202608041223-cover-inherited-js-impl-brand.md +4 -0
  21. package/history/202608041911-typed-data-shape-patch.md +30 -0
  22. package/history/202608042252-reject-decoded-edn-collisions.md +11 -0
  23. package/history/202608042338-strict-edn-js-collision-parity.md +21 -0
  24. package/history/202608042344-isolate-strict-edn-dependency-test.md +13 -0
  25. package/history/202608061335-recursive-nominal-display.md +28 -0
  26. package/lib/calcit.procs.d.mts +35 -4
  27. package/lib/calcit.procs.mjs +233 -11
  28. package/lib/custom-formatter.mjs +28 -2
  29. package/lib/js-cirru.d.mts +2 -0
  30. package/lib/js-cirru.mjs +39 -17
  31. package/lib/js-enum.mjs +1 -1
  32. package/lib/js-impl.d.mts +1 -0
  33. package/lib/js-impl.mjs +28 -0
  34. package/lib/js-record.mjs +1 -1
  35. package/lib/js-struct.mjs +1 -1
  36. package/lib/package.json +3 -2
  37. package/lib/typed-edn.d.mts +4 -0
  38. package/lib/typed-edn.mjs +5 -0
  39. package/package.json +3 -2
  40. package/ts-src/calcit.procs.mts +241 -13
  41. package/ts-src/custom-formatter.mts +55 -2
  42. package/ts-src/js-cirru.mts +39 -19
  43. package/ts-src/js-enum.mts +1 -1
  44. package/ts-src/js-impl.mts +33 -0
  45. package/ts-src/js-record.mts +1 -1
  46. package/ts-src/js-struct.mts +1 -1
  47. package/ts-src/typed-edn.mts +7 -0
@@ -0,0 +1,30 @@
1
+ # Nominal Option API migration and JS FFI diagnostics
2
+
3
+ ## 修改概要
4
+
5
+ - 直接将 `find`、`find-index`、`index-of` 的公开返回值迁移为名义 `Option`,不再保留并行的 safe API;
6
+ - 将公开 `parse-float` 改为 `Result<Number,String>`,将公开 `get-env` 改为单参数 `Option<String>`,nullable 宿主过程下沉为 `&parse-float` 与 `&get-env`;
7
+ - 对未绑定泛型只把 payload 降为 Dynamic,保留 `Option<Dynamic>` 等外层名义类型,避免迁移信息整体退化为 Dynamic;
8
+ - 新增 `W_NOMINAL_ENUM_LEGACY_USE`,发现 Option/Result 继续被 `some?`、`nil?`、位置读取或底层比较按旧 nullable 值消费;
9
+ - 将 JS 属性读取、原生方法调用、`aget`/`js-get` 与 `js/...` 调用推断为 `Optional<JsObject>`,并新增 `W_JS_FFI_NULLABLE_DEREF`,不把宿主空值自动转换成 Option;
10
+ - 统一 Native、JS、WASM 中空 List 的 `rest`/`butlast` 行为,并收紧 `rest`、`empty` 的同类型返回契约;
11
+ - 更新 core examples、Native/JS/WASM 测试、迁移 RFC 与 JS interop/Option 文档。
12
+
13
+ ## 知识点
14
+
15
+ - breaking API 只有在外层名义类型不被 Dynamic 吞掉时才具有可迁移性;未知 payload 应表示为 `Option<Dynamic>`,而不是把整个调用结果退回 Dynamic;
16
+ - `some?`/`nil?` 只适合 `Optional<T>` 兼容边界。名义 Option 必须使用 `option:some?`、`option:none?`、unwrap 或 `tag-match`,否则 `%none` 作为非 nil tuple 会造成分支静默反转;
17
+ - JS FFI 的两个风险维度必须同时保留:`Optional` 表示 `null`/`undefined`,`JsObject` 表示 payload 仍是未验证的宿主对象。nil guard 不能把 JsObject 自动证明成 Number/String;
18
+ - `first`、`last`、`nth`、`get` 参与 core 宏自举,不能在宏迁移到明确低层原语前直接改成 Option;否则 `let` 等宏会在加载阶段收到名义 tuple;
19
+ - 完整 core Snapshot 的递归预处理和 context 渲染需要与 CLI 相同的 16 MiB 栈预算,Rust 默认测试线程栈不足以覆盖增长后的 core AST。
20
+
21
+ ## 验证
22
+
23
+ - `cargo fmt --all`
24
+ - `cargo clippy -- -D warnings`
25
+ - `cargo test`(346 lib + 2 caps + 180 cr)
26
+ - `yarn check-all`(Native、JS、IR、WASM 全通过)
27
+ - `yarn check-agent-interface`(12/12,通过 `check-all` 执行)
28
+ - `cr docs check-md docs/features/polymorphism.md --entry calcit/test.cirru --failures-only`
29
+ - `cr docs check-md docs/features/js-interop.md --entry calcit/test.cirru --failures-only`
30
+ - 安装当前 `cr` 后只读检查 Respo:报告 3 条 Option 迁移、1 条 `get-env` arity、10 条 JS FFI nullable dereference、6 条 JS/Unit 返回不匹配;Respo 工作区保持干净。
@@ -0,0 +1,31 @@
1
+ # Expand nominal Option collection APIs
2
+
3
+ ## 修改概要
4
+
5
+ - 将 `&list:find-last`、`&list:find-last-index`、`&list:last-index-of` 的缺失结果迁移为 `Option`;
6
+ - 将 List/Set 的 `max`、`min` 迁移为 `Option<Number>`,空集合明确返回 `%none`;
7
+ - 保留内部 `&str:find-index` 的数值哨兵 ABI,新增公开 `str-find-index: String,String -> Option<Number>`,字符串 `.find-index` 指向公开 API;
8
+ - 保留内部 `&get-in: Dynamic,List<K> -> Optional<Dynamic>`,公开 `get-in` 通过 `optionally` 返回 `Option<Dynamic>`,字面量路径推断继续保留精确 payload;
9
+ - 修正 WASM 针对 find/find-index/max/min 的优化发射路径,确保它们与 Native、JS 一样构造 `%some`/`%none` 名义 tuple;
10
+ - 扩展 `W_NOMINAL_ENUM_LEGACY_USE`:拦截 Option/Result 与 payload 混合比较、payload 类型谓词、tuple/collection 表示操作和直接 truthiness;同一名义枚举之间的相等比较仍然允许;
11
+ - 更新测试与文档中的旧 nullable 用法。
12
+
13
+ ## 知识点
14
+
15
+ - 公开 API 应返回名义 `Option`,但内部递归、宿主 ABI 和底层哨兵可以继续使用 `Optional`/数值哨兵;两层接口要用命名和 `:internal` 标签清楚分隔;
16
+ - JS FFI 的 `Optional<JsObject>` 不能沿用普通数据 API 的 Option 包装策略。它同时表达宿主空值和未验证对象,必须由专门的 nullable dereference 与返回类型诊断负责;
17
+ - `%none` 本身是 truthy tuple。迁移诊断不能只检查 `nil?`/`some?`,还要检查 `if option`、`record? option`、`count option`、Option 与 payload 的 `=`,否则 breaking change 可能静默改变控制流;
18
+ - 相等比较的告警需要检查所有参数:`Option == Option` 合法,`Option == payload` 或 `Option == Dynamic` 才是迁移风险;
19
+ - WASM 的专用优化 emitter 会绕过普通 core 函数体。只改 Native/JS/runtime 定义不足以改变 WASM 语义,名义返回值必须在 emitter 中显式分配 enum tuple;
20
+ - `cr-wasm` 会嵌入构建时的 core Snapshot。验证未提交 core 改动时应重新构建当前二进制,并通过 `CR_WASM_BIN` 明确选择它,避免旧 release 产物造成假失败。
21
+
22
+ ## 验证
23
+
24
+ - `cargo fmt`
25
+ - `cargo clippy -- -D warnings`
26
+ - `cargo test`(346 lib + 2 caps + 180 cr)
27
+ - `yarn compile`
28
+ - `CR_WASM_BIN=./target/debug/cr-wasm yarn check-all`(Native、JS、IR、WASM 全通过)
29
+ - `yarn check-agent-interface`(12/12)
30
+ - 四份变更文档的 `cr docs check-md`(58/58 blocks)
31
+ - 安装当前全局 `cr` 后只读检查 Respo:4 条 Option 迁移、1 条旧 `get-env` arity、10 条 JS FFI nullable dereference、6 条 JS/Unit 返回不匹配;JS FFI 告警未被 Option 迁移吞掉,Respo 工作区保持干净。
@@ -0,0 +1,29 @@
1
+ # Separate JavaScript nullish boundaries
2
+
3
+ ## 修改概要
4
+
5
+ - 新增 `JsNullish<T>` 类型标注,专门表达 JavaScript FFI 的 `null`/`undefined` 边界,并补齐解析、序列化、泛型替换、类型覆盖和诊断展示;
6
+ - 将 raw `js/...`、预处理后的 `RawCode`、原生属性读取与方法调用、`aget`/`js-get` 的保守返回类型改为 `JsNullish<JsObject>`;
7
+ - 明确禁止 `JsNullish<T>` 与遗留 `Optional<T>` 相互匹配,避免通用 `optionally` 把宿主空值静默包装为 `Option`;
8
+ - 新增 `js-nullish?`、`js-present?` 和显式 `js-nullish->option`,并为旧 `nil?`/`some?` 判空、不安全 FFI 解引用提供专用诊断与分支收窄;
9
+ - 对非 core 函数 schema 中的 `Optional<T>` 发出阻断性 `W_LEGACY_OPTIONAL_SCHEMA`,普通缺失改用 `Option`,失败改用 `Result`,副作用返回改用 `Unit`;core 自举依赖的旧 nullable API 暂时保留内部兼容表示;
10
+ - 更新 JS 集成用例、静态分析文档与 nil reduction RFC,不引入 `Nilable`。
11
+
12
+ ## 知识点
13
+
14
+ - `js/...` 符号在预处理后会变成 `Calcit::RawCode`;只在符号推断分支处理 FFI 会漏掉真实项目,因此 standalone RawCode 与 RawCode call head 都必须保留 `JsNullish<JsObject>`;
15
+ - `JsNullish<T>` 是宿主边界的 union-like 标注,不是 Calcit 业务数据。判空只证明 payload 存在,不能把 opaque `JsObject` 自动证明为 `Number`、`String` 或记录;可信契约必须显式 `unsafe-coerce`,普通代码优先使用 decoder;
16
+ - Optional 与 JsNullish 的双向不匹配是迁移安全约束:即使两者运行时都可能由 nil 承载,类型系统也不能让 JS 空值沿旧兼容 API 静默进入名义 Option;
17
+ - 全局 `document`、`localStorage`、`Math`、`Date` 等也按保守 FFI 边界处理。可选原生访问适合真实可空路径,已知宿主契约则在最小边界处显式收窄或转换;
18
+ - 公开 Optional 先采用可聚合的阻断 warning,而不是首条即退出的 schema error,这样 Respo 等大项目一次检查即可列出全部迁移点。
19
+
20
+ ## 验证
21
+
22
+ - `cargo fmt --all`
23
+ - `cargo clippy -- -D warnings`
24
+ - `cargo test`(349 lib + 2 caps + 180 cr)
25
+ - `yarn compile`
26
+ - `CR_WASM_BIN=./target/debug/cr-wasm yarn check-all`(Native、JS、Agent interface、WASM 全通过)
27
+ - `yarn check-agent-interface`(12/12)
28
+ - 三份变更文档的 `cr docs check-md`(64/64 blocks)
29
+ - 安装当前全局 `cr` 后只读检查 Respo:共 55 条迁移诊断,其中 18 条不安全 JS 解引用、4 条旧 JS 判空、16 条公开 Optional schema、11 条函数参数/返回类型不匹配、1 条底层 proc 参数不匹配、4 条名义枚举旧用法、1 条 `get-env` arity;Respo 工作树保持干净。
@@ -0,0 +1,29 @@
1
+ # Nominal reflection and destruct APIs
2
+
3
+ ## 修改概要
4
+
5
+ - 新增公开反射边界:`tuple-enum` 返回 `Option<Enum>`,`impl-origin` 返回 `Option<Trait>`,`record-struct` 返回确定存在的 `Struct`;底层 `&tuple:enum`、`&impl:origin` 保留为内部 nullable primitive,`&record:struct` 的签名修正为非空返回;
6
+ - 将 `destruct-list`、`destruct-map`、`destruct-set`、`destruct-str` 从匿名 `:: :some/:none` tuple 迁移为 `ListDestruct<T>`、`MapDestruct<K,V>`、`SetDestruct<T>`、`StringDestruct` 具名枚举,使分支和 payload 类型可由预处理器检查;
7
+ - 迁移核心测试、文档和示例到公开名义 API,并为 `record-struct` 补齐 WASM 公共包装层的内联 codegen;
8
+ - 收紧 legacy nil/tuple 诊断:只识别 canonical `calcit.core` 操作与 `calcit.core/Option`、`calcit.core/Result`,不再误报应用自定义的 `get`、`Option` 或枚举定义值;
9
+ - 修正 JS 边界一致性:`parse-float` 必须消费完整十进制字符串,`get-env` 缺失时返回 Calcit nil 而不是泄漏 JavaScript `undefined`;
10
+ - 收紧 native proc schema 回归测试,只允许带省略参数元数据的 proc 省略实参,并完整断言 nullable 返回类型。
11
+
12
+ ## 知识点与兼容边界
13
+
14
+ - 反射 primitive 的 nil 是运行时表示细节,不应穿透公开 API;公开层必须把“可能不存在”提升为 `Option`,把运行时保证存在的记录来源声明为总函数;
15
+ - 集合解构不是单纯的可选值:成功分支还携带剩余集合,因此使用专用泛型枚举比嵌套 tuple 或 `Option<Tuple>` 更能保留 payload 关系;
16
+ - 名义迁移诊断必须同时限定操作来源和枚举完整路径;仅按符号名匹配会污染用户空间,并曾导致 tuples 文档把 `Result` 枚举定义本身误判为旧式值;
17
+ - WASM 当前可直接内联 `record-struct`;`tuple-enum` 和 `impl-origin` 仍受 WASM 后端尚未保留 enum/trait 反射元数据的既有边界限制;
18
+ - core 弱类型审计目前剩 16 个 code-nil:15 个未解析位置主要属于宏 AST sentinel/自举兼容,1 个是 `last` 的遗留 `Optional`。`first`/`last`/`nth`/`get` 的下一阶段迁移需要连同 method inliner 与 core bootstrap 一起设计,不能机械替换。
19
+
20
+ ## 验证
21
+
22
+ - `cargo fmt --all`
23
+ - `cargo clippy -- -D warnings`
24
+ - `cargo test`(349 lib + 2 caps + 180 cr)
25
+ - `yarn compile`
26
+ - `CR_WASM_BIN=./target/debug/cr-wasm yarn check-all`(Native、JS、IR、Agent interface、WASM 全通过)
27
+ - 7 份变更文档通过 `cr docs check-md --entry calcit/test.cirru`
28
+ - 新公开 API 与四个 destruct 枚举的定向 examples 和静态类型断言通过
29
+ - 安装当前全局 `cr` 后只读检查 Respo:294 个定义完成类型覆盖分析,`respo.util.format/get-style-value` 的 3 个示例通过;诊断能发现该项目现存 legacy Optional 与不安全 JsNullish 解引用。
@@ -0,0 +1,36 @@
1
+ # Public nil contract freeze
2
+
3
+ ## 修改概要
4
+
5
+ - 将公开 `first`、`last`、`nth`、`get`、`get-in` 及其方法入口统一为名义 `Option<T>`,同时把 core 自举和已证明安全的内部路径迁到 `&list:*`、`&map:*`、`&record:*` raw primitive;
6
+ - 修复公开方法表泄漏:Map/Set `.destruct` 返回具名 `MapDestruct`/`SetDestruct`;Record 不再暴露跨后端顺序不稳定的 `.nth`,字段读取统一走返回 `Option<T>` 的 `get`;
7
+ - 将 `when-let` 固定为 `Option<T> -> Option<R>`,将 `update-in` updater 固定为 `Option<T> -> T`,缺失叶子不再通过 nil 传递;
8
+ - 非穷尽 `case` 改为明确报错,`cond` 要求最终 `true` 分支;`dissoc-in` 空路径改为保持输入值;
9
+ - 公开 core 与应用定义若暴露 legacy `Optional<T>` 会触发 `W_LEGACY_OPTIONAL_SCHEMA`,仅 `&` raw primitive 与 `optionally` 桥接豁免;
10
+ - `;nil` 明确定义为 Unit marker;弱类型审计把 Unit 定义内的 nil 归为 `declared-unit`,core 的 unresolved/declared-optional code-nil 门禁归零;
11
+ - 更新原生/JS/WASM 测试、类型推断用例、RFC 和迁移文档,集中声明下一版本是 nil 迁移的最终 breaking window。
12
+ - 保留 JS runtime 的 legacy `get_env` 导出并委托给内部 `_$n_get_env`,使升级 npm runtime 时尚未重新生成的 JS bundle 仍可启动;新生成源码仍只能通过 typed `get-env -> Option<String>` 或内部 raw proc,不把兼容别名重新暴露为 Calcit API。
13
+
14
+ ## 知识点与兼容边界
15
+
16
+ - raw primitive 可以保留 nullable ABI,但不得直接挂入公开 method table;方法表本身是公开 API 审计的一部分;
17
+ - 类型系统只能检查重新预处理的 Calcit 源码,无法检查用户手中已经生成的旧 JS bundle;因此底层 JS export 改名还需要一层薄兼容别名,避免“旧 bundle + 新 npm runtime”直接在模块加载阶段崩溃;
18
+ - 静态已知 Record 字段 tag access 是总操作,可由编译器优化为内部 `&record:nth`;公开 API 不承诺字段位置顺序,只提供带 `Option` 外层的字段名 `get`;
19
+ - partial record 的字段名属于结构,但 payload 仍可能是 nil,因此合法结果可以是 `%some nil`;`%none` 只表示键/索引不存在;
20
+ - `update-in` 的缺失与“存在且值为 nil”必须由 `%none` / `%some nil` 区分,updater 不再猜测裸 nil 的含义;
21
+ - `Unit` 仍由运行时 nil 承载,但源码使用 `;nil` 只表达无业务值,不能作为 Option 的替代品;
22
+ - 下一版本发布后,新增缺失 API 必须首次发布即采用 Option/Result/Unit。以后不得再以清理 nil 为由改变同一公开返回类型。
23
+
24
+ ## 验证清单
25
+
26
+ - `cargo fmt --all -- --check`:通过;
27
+ - `cargo clippy -- -D warnings`:通过;
28
+ - `cargo test`:通过(lib 349、caps 2、cr 180,另含 doc tests);
29
+ - `yarn compile`:通过;
30
+ - `yarn check-all`:通过,覆盖 Native、JS、IR、WASM 与 Agent interface;
31
+ - `yarn check-agent-interface`:12/12 通过;
32
+ - core `analyze weak-types --only code-nil --intent unresolved,declared-optional`:零结果;
33
+ - 变更涉及的 Markdown examples:全部通过 `docs check-md`;
34
+ - 全局安装当前工作树构建的 `cr` 后,对 Respo 核心库执行只读回归:`--check-only` 在旧代码把 `first` 的 `Option` 结果继续交给 `&list:nth` 时立即失败,证明升级断点可发现;`analyze weak-types --only code-nil --intent unresolved,declared-optional --summary-only` 报告 57 处待迁移 nil(54 unresolved、3 declared-optional),为下游一次性迁移提供清单。该回归不修改 Respo。
35
+ - 在临时 Respo 副本修正宏展开的首个 Option 断点后,`--check-only` 继续以 60 条诊断阻断旧源码,其中 17 条 `W_JS_FFI_NULLABLE_DEREF`、5 条 `W_FN_ARG_TYPE_MISMATCH`、8 条 `W_FN_RETURN_TYPE_MISMATCH`;覆盖 `if-let` 误收 JsNullish、未收窄的 DOM 解引用、FFI 返回冒充 Unit、`Date.now` 未验证即参与数值运算等真实边界。
36
+ - 用 Respo 已生成的浏览器 bundle 强制链接当前工作树 `lib/calcit.procs.mjs`:首次加载发现缺失 legacy `get_env` export;补兼容别名后,干净页面加载无 console error/warning,并成功输入、添加一条 Todo,证明 DOM 渲染与事件 FFI 路径可运行。整个过程只修改 `/private/tmp` 副本,不修改 Respo 原仓库。
@@ -0,0 +1,4 @@
1
+ # 修复 HashMap Option 文档示例
2
+
3
+ - 将 `get` 的缺失值示例改为使用 `option:unwrap-or`,避免把名义 `Option` 当作 `nil`。
4
+ - 修复词频统计示例,使文档检查覆盖并示范稳定的代数数据类型 API。
@@ -0,0 +1,5 @@
1
+ # 发布 0.13.0
2
+
3
+ - 将 Cargo crate 与 npm package 的版本同步升级到 0.13.0。
4
+ - 该 minor 版本承载 Option、Result、Unit 与 JsNullish 公共契约的集中 breaking change,作为后续 nil 安全 API 的稳定基线。
5
+ - 发布前后继续通过 Calcit 全量验证与 Respo 真实页面回归,避免将未被类型系统发现的 JS FFI 兼容问题带入后续版本。
@@ -0,0 +1,6 @@
1
+ # 发布 0.13.1
2
+
3
+ - 将 Cargo crate 与 npm package 的版本同步升级到 0.13.1。
4
+ - 本 patch 修复自递归 nominal struct 的类型注解解析,避免 Optional 自引用在定义时递归展开。
5
+ - record、struct 与 enum 的展示名称统一为 symbol;JS DevTools formatter 同步展示 nominal 定义与 enum payload。
6
+ - 发布前通过 Rust、TypeScript 与全量集成验证。
@@ -0,0 +1,6 @@
1
+ # JS runtime impl identity
2
+
3
+ - Fixed #293 by branding `CalcitImpl` values with a shared `Symbol.for` key, so duplicate `@calcit/procs` module instances agree on impl identity.
4
+ - Re-interned cloned impl field tags in the receiving runtime to avoid the same cross-module identity problem during `castTag`.
5
+ - Added a Node regression check that loads two complete runtime copies and promotes an impl value across them.
6
+ - Verified the patched runtime in a temporary Timegrass snapshot with Node 24 and Vite 8.
@@ -0,0 +1,27 @@
1
+ # Strict Cirru EDN decoding
2
+
3
+ ## 背景
4
+
5
+ `parse-cirru-edn` 面向动态 EDN。它的 options map 只能按名称恢复部分 record/enum 身份,不能证明容器元素、字段、enum payload、泛型实参和约束符合业务类型;字段集合不一致时,Native 旧路径还可能触发 panic。
6
+
7
+ ## 本次决策与实现
8
+
9
+ - 先形成 `RFCs/08-04-strict-cirru-edn-decoding-rfc.md`,把动态解析与严格类型边界分开。
10
+ - 新增 language syntax `parse-cirru-edn-as text TypeExpr`;预处理阶段要求目标类型闭合且可解码,静态推断结果直接采用目标类型。
11
+ - 编译器把类型表达式派生为不含 `Dynamic` fallback 的 `EdnDecoderGraph`。Native 从 `cirru_edn::Edn` 深度验证并构造 Calcit 值,JS codegen 输出同构 graph 交给 runtime 执行。
12
+ - struct/enum 解码检查精确名称、字段集合、variant、payload arity 和深层字段类型,并保留真实 nominal declaration;泛型应用检查参数数量和 `:where` 约束。
13
+ - Native/JS 错误都携带结构路径,例如 `$[0].age`。
14
+ - JS nominal struct 的字段顺序由 interned tag 决定,可能不同于 Rust 声明解析使用的词法顺序;JS 严格解码先按 graph 校验,再按运行时 `nominal.fields` 重排 values。
15
+ - 旧动态 record options 字段不匹配时改为返回 loose record,避免 `unreachable!` panic;JS 保持相同行为。
16
+
17
+ ## 边界与后续
18
+
19
+ - Phase 1 支持有限闭合类型图,不承诺递归 struct/enum。现有自递归名义类型在预处理构造路径会栈溢出,已记录为 GitHub Issue #295;修复 cycle-safe 类型解析后再开放递归 decoder。
20
+ - 一等、不可伪造的 `EdnDecoder<T>` dictionary 延后到 Phase 2。当前 graph 只作为编译器内部闭合程序,避免重新暴露动态 schema 逃生口。
21
+ - Timegrass 的 snapshot 临时副本已使用当前 `cr --check-only` 回归通过,未写入业务仓库。
22
+
23
+ ## 验证要点
24
+
25
+ - Rust 单测覆盖深层 struct/enum、错误路径、Dynamic/缺泛型拒绝、where-bound,以及旧动态 record mismatch 非 panic。
26
+ - `calcit/test-edn.cirru` 同时覆盖 Native 与 JS:本地及 imported nominal type、嵌套容器、泛型 struct、enum、运行时失败和 top-level decode 顺序。
27
+ - 文档示例由 `cr docs check-md` 验证。
@@ -0,0 +1,16 @@
1
+ # Strict EDN review follow-ups
2
+
3
+ ## Review findings handled
4
+
5
+ - Preprocessing now retains the derived `EdnDecoderGraph` as an internal `AnyRef` handle. Native evaluation and JS codegen reuse that graph instead of parsing and deriving the target type on every evaluation/codegen pass.
6
+ - `collect_compiled_deps` reads nominal paths from the retained graph, so same-namespace struct/enum declarations participate in JS top-level dependency ordering. The integration fixture deliberately names the decoded value `A-typed-person`, lexically before `Person`, and verifies codegen still initializes `Person` first.
7
+ - Native strict record decoding rejects duplicate EDN field names instead of collapsing them through `HashSet` comparison and selecting the first value.
8
+ - Recursive `TypeSlot` resolution has a dedicated in-progress guard and returns a type error instead of overflowing the preprocessing stack.
9
+ - JS runtime validates decoder-vs-nominal field alignment and unknown decoder node kinds explicitly.
10
+ - The decoder module and graph types are crate-private, matching the RFC boundary for Phase 1.
11
+ - Dynamic record fallback construction was simplified without changing its compatibility behavior.
12
+
13
+ ## Review decisions
14
+
15
+ - The RFC/history date remains 2026-08-04. GitHub review timestamps were displayed in UTC on August 3, while the repository task timezone was Asia/Shanghai and local time was already August 4.
16
+ - Await-aware wrapping was applied to the generated strict decode call for consistency. With an empty argument prelude it is behaviorally equivalent, while the surrounding function/top-level code still owns async context detection.
@@ -0,0 +1,5 @@
1
+ # Harden JS impl brand check
2
+
3
+ - `CalcitImpl[Symbol.hasInstance]` now reads its shared brand from an own property descriptor, so inherited brands cannot spoof an implementation value.
4
+ - The check also verifies the own implementation fields needed by consumers before accepting a cross-runtime value.
5
+ - The JS runtime identity regression check covers inherited and malformed branded objects.
@@ -0,0 +1,4 @@
1
+ # Cover inherited JS impl brand rejection
2
+
3
+ - Completed the inherited-brand fixture with the normal own `CalcitImpl` fields.
4
+ - The runtime identity check now proves that the own-brand requirement, rather than a missing field, rejects the fixture.
@@ -0,0 +1,30 @@
1
+ # Typed data shapes and nominal patch foundation
2
+
3
+ ## 背景
4
+
5
+ 严格 EDN 解码已经能在反序列化边界恢复闭合静态类型,但其类型图原先仍属于解码器私有实现。struct 更新和 Recollect diff/patch 也缺少同一套可校验的名义数据约束,容易在新的数据边界重新引入 Dynamic。
6
+
7
+ ## 本次实现
8
+
9
+ - 将解码器类型图提取为 backend-neutral 的 `DataShapeGraph`,保留 ABI version、稳定 fingerprint、名义类型路径和闭合子节点关系。
10
+ - 为 `DataShapeGraph` 增加运行时值验证,覆盖标量、容器、ref、struct 和 enum;struct/enum 必须匹配名义定义、字段或 variant 及递归 payload 类型。
11
+ - 严格 EDN Native/JS 路径复用同一 data shape;JS graph 同时携带 ABI version 和 fingerprint,并拒绝畸形 graph 元数据。
12
+ - 新增内部 `DataPatch` 第一阶段执行器,支持 keep、replace、struct 字段 patch 和 enum payload patch。patch 与 shape 的 ABI/fingerprint 必须一致,且应用后继续保持原有 struct/enum 名义对象。
13
+ - struct indexed 更新同时校验编译期 index 与 field tag,并拒绝负数、非整数及越界索引,避免 schema 演化后静默更新相邻字段。
14
+ - 类型推断对 record assoc/with 的普通与 indexed 形式保留精确 receiver 类型,包括泛型实参;预处理同步检查更新值类型。
15
+
16
+ ## Recollect 联动
17
+
18
+ - `change-op` 的真实构造迁移到名义 enum,避免 diff 结果退化为无约束 tuple。
19
+ - `:map-splice` 的 removed payload 修正为 Set,与 diff 产物及 patch 消费端一致。
20
+ - collection diff 策略暂时保留在 Recollect;后续应按 `DataShapeNode` 绑定 list/map/set 的 typed patch 策略,而不是把 Dynamic collection payload 直接搬进编译器核心。
21
+
22
+ ## 验证
23
+
24
+ - `cargo fmt -- --check`
25
+ - `cargo clippy -- -D warnings`
26
+ - `cargo test -- --test-threads=1`:513 项通过
27
+ - `yarn compile`
28
+ - `yarn check-all`
29
+ - `yarn check-agent-interface`:12/12 通过
30
+ - Recollect check-only 零告警,Native、JavaScript 和阻断性 WASM 回归通过
@@ -0,0 +1,11 @@
1
+ # Reject decoded EDN collection collisions
2
+
3
+ ## Change summary
4
+
5
+ - Strict typed EDN set decoding now rejects distinct source items that decode to the same Calcit value, reporting the colliding `$.item` path.
6
+ - Strict typed EDN map decoding now rejects distinct source keys that decode to the same Calcit key, reporting the colliding `$.key` path before decoding or overwriting the associated value.
7
+ - Regression tests cover set-value and map-key collisions caused by record field-order normalization, along with successful decoding when collection entries remain unique.
8
+
9
+ ## Knowledge point
10
+
11
+ EDN collection uniqueness is not sufficient after typed decoding because normalization can make distinct EDN values equal as Calcit values. Collision checks must therefore use the decoded collection's equality semantics immediately before insertion.
@@ -0,0 +1,21 @@
1
+ # Strict EDN JS collision parity and final review
2
+
3
+ ## Knowledge
4
+
5
+ - Strict typed decoding must observe source collection entries before runtime collection constructors normalize or deduplicate them; otherwise decoded-value collisions cannot be detected reliably.
6
+ - Exact duplicate source set items keep ordinary set semantics, and exact duplicate source map keys keep last-value-wins semantics. Distinct source values that normalize to the same typed value must instead fail with an item/key path.
7
+ - Data-shape validation promises nominal identity, so structurally equal struct or enum declarations are insufficient; validation must retain and compare the exact declaration `Arc`.
8
+
9
+ ## Changes
10
+
11
+ - Added an internal typed-EDN set view and a strict JS extraction path that preserves collision evidence without changing dynamic `parse-cirru-edn` behavior.
12
+ - Added JS set/map decoded-collision checks and shared Native/JS integration fixtures for collisions and exact source duplicates.
13
+ - Tightened struct/enum data-shape validation to pointer identity and fixed the RFC blockquote lint warning.
14
+
15
+ ## Validation
16
+
17
+ - `cargo fmt --all`
18
+ - `cargo test calcit::data_shape::tests::rejects_structurally_equal_but_distinct_nominal_declarations`
19
+ - `yarn compile`
20
+ - `yarn try-rs`
21
+ - `yarn try-js`
@@ -0,0 +1,13 @@
1
+ # Isolate strict EDN dependency test state
2
+
3
+ ## Knowledge
4
+
5
+ - Program dependency tests share the global definition-ID index with cache and snapshot tests, so every test that calls `ensure_def_id` must use `PROGRAM_TEST_LOCK` and reset the shared state before asserting IDs.
6
+
7
+ ## Changes
8
+
9
+ - Added the existing program-test lock and reset protocol to the strict EDN nominal dependency test.
10
+
11
+ ## Validation
12
+
13
+ - `cargo test`
@@ -0,0 +1,28 @@
1
+ # Recursive nominal types and symbol presentation
2
+
3
+ ## Background
4
+
5
+ `defstruct` field annotations eagerly resolved a reference to their own
6
+ definition. A recursive field such as `(:: 'Optional Node)` therefore expanded
7
+ `Node` while it was being declared and could exhaust the Rust stack. In
8
+ addition, nominal record, struct, and enum names were displayed as tags even
9
+ though they are type symbols.
10
+
11
+ ## Changes
12
+
13
+ - Preserve a strict self-reference as `TypeRef` during type annotation parsing,
14
+ while keeping normal trait reference resolution unchanged.
15
+ - Add native and snapshot regression coverage for nil and nested recursive
16
+ nodes, plus a required recursive field that produces a regular type error.
17
+ - Render record, struct, and enum names as quoted symbols in Rust and the JS
18
+ runtime.
19
+ - Extend the browser custom formatter to expose nominal struct field types and
20
+ enum payload types, with structured runtime checks for the formatter output.
21
+
22
+ ## Verification
23
+
24
+ - `cargo fmt --check`
25
+ - `cargo clippy -- -D warnings`
26
+ - `cargo test`
27
+ - `yarn compile`
28
+ - `yarn check-all`
@@ -17,6 +17,7 @@ export declare const calcit_package_json: {
17
17
  "test-rs": string;
18
18
  "test-fail": string;
19
19
  "test-snippets": string;
20
+ "check-js-runtime": string;
20
21
  "bench-recur-smoke": string;
21
22
  "check-smooth": string;
22
23
  "check-all": string;
@@ -143,11 +144,12 @@ export declare function _$n_str_$o_empty_$q_(xs: CalcitValue): boolean;
143
144
  export declare function _$n_map_$o_empty_$q_(xs: CalcitValue): boolean;
144
145
  export declare function _$n_set_$o_empty_$q_(xs: CalcitValue): boolean;
145
146
  export declare let _$n_list_$o_first: (xs: CalcitValue) => CalcitValue;
147
+ export declare let _$n_list_$o_last: (xs: CalcitValue) => CalcitValue;
146
148
  export declare let _$n_str_$o_first: (xs: CalcitValue) => CalcitValue;
147
149
  export declare let _$n_map_$o_destruct: (xs: CalcitValue) => CalcitValue;
148
150
  export declare let _$n_set_$o_destruct: (xs: CalcitValue) => CalcitValue;
149
151
  export declare let timeout_call: (duration: number, f: CalcitFn) => null;
150
- export declare let _$n_list_$o_rest: (xs: CalcitValue) => CalcitValue;
152
+ export declare let _$n_list_$o_rest: (xs: CalcitValue) => CalcitList | CalcitSliceList;
151
153
  export declare let _$n_str_$o_rest: (xs: CalcitValue) => CalcitValue;
152
154
  export declare let recur: (...xs: CalcitValue[]) => CalcitRecur;
153
155
  export declare let _$n_get_calcit_backend: () => CalcitTag;
@@ -155,7 +157,7 @@ export declare let not: (x: boolean) => boolean;
155
157
  export declare let prepend: (xs: CalcitValue, v: CalcitValue) => CalcitList;
156
158
  export declare let append: (xs: CalcitValue, v: CalcitValue) => CalcitList | CalcitSliceList;
157
159
  export declare let last: (xs: CalcitValue) => CalcitValue;
158
- export declare let butlast: (xs: CalcitValue) => CalcitValue;
160
+ export declare let butlast: (xs: CalcitValue) => CalcitList | CalcitSliceList | string;
159
161
  export declare let initCrTernary: (x: string) => CalcitValue;
160
162
  export declare let _SHA__$M_: (...xs: CalcitValue[]) => CalcitValue;
161
163
  export declare let generate_id_$x_: () => string;
@@ -193,7 +195,7 @@ export declare let split: (xs: string, x: string) => CalcitSliceList;
193
195
  export declare let split_lines: (xs: string) => CalcitSliceList;
194
196
  export declare let _$n_str_$o_slice: (xs: string, m: number, n: number) => string;
195
197
  export declare let _$n_str_$o_find_index: (x: string, y: string) => number;
196
- export declare let parse_float: (x: string) => number | null;
198
+ export declare let _$n_parse_float: (x: string) => number | null;
197
199
  export declare let trim: (x: string, c: string) => string;
198
200
  export declare let _$n_number_$o_format: (x: number, n: number) => string;
199
201
  export declare let _$n_number_$o_display_by: (x: number, n: number) => string;
@@ -206,7 +208,8 @@ export declare let js_get: (x: any, name: string) => any;
206
208
  export declare let js_set: (x: any, name: string, v: any) => any;
207
209
  /** generates `delete a.b` */
208
210
  export declare let js_delete: (obj: any, name: string) => any;
209
- export declare let get_env: (name: string, v0: string) => string;
211
+ export declare let _$n_get_env: (name: string, v0?: CalcitValue) => CalcitValue;
212
+ export declare let get_env: (name: string, v0?: CalcitValue) => CalcitValue;
210
213
  export declare let turn_tag: (x: CalcitValue) => CalcitTag;
211
214
  export declare let turn_symbol: (x: CalcitValue) => CalcitSymbol;
212
215
  export declare let to_lispy_string: (...args: CalcitValue[]) => string;
@@ -242,6 +245,34 @@ export declare let write_file: (path: string, content: string) => void;
242
245
  export declare let parse_cirru: (code: string) => CalcitCirruQuote;
243
246
  export declare let parse_cirru_list: (code: string) => CalcitList;
244
247
  export declare let parse_cirru_edn: (code: string, options: CalcitValue) => CalcitValue;
248
+ type DataShapeNode = {
249
+ kind: "unit" | "bool" | "number" | "string" | "symbol" | "tag" | "buffer" | "cirru-quote";
250
+ } | {
251
+ kind: "optional" | "list" | "set" | "ref";
252
+ inner: number;
253
+ } | {
254
+ kind: "map";
255
+ key: number;
256
+ value: number;
257
+ } | {
258
+ kind: "struct";
259
+ nominal: CalcitStruct;
260
+ fields: Array<[string, number]>;
261
+ } | {
262
+ kind: "enum";
263
+ nominal: CalcitEnum;
264
+ variants: Array<{
265
+ tag: string;
266
+ payload: number[];
267
+ }>;
268
+ };
269
+ type DataShapeGraph = {
270
+ version: number;
271
+ root: number;
272
+ fingerprint: string;
273
+ nodes: DataShapeNode[];
274
+ };
275
+ export declare let parse_cirru_edn_as: (code: string, graph: DataShapeGraph) => CalcitValue;
245
276
  export declare let json_parse: (code: CalcitValue) => CalcitValue;
246
277
  export declare let json_stringify: (value: CalcitValue) => string;
247
278
  export declare let json_pretty: (value: CalcitValue) => string;