@calcit/procs 0.13.58 → 0.13.60

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.
Binary file
@@ -0,0 +1,39 @@
1
+ # Snapshot Symbol Keys and `tag-match` Deprecation RFC
2
+
3
+ Status: Implemented / 已实现
4
+
5
+ ## 中文
6
+
7
+ ### 背景
8
+
9
+ Architecture 文件已经用 Symbol 表示 definition FQN,而 `calcit.cirru` 的 `:files` 与 `:defs` key 仍由 formatter 写成 String。两套持久化表示增加了工具转换成本,也使“标识符”和“普通文本”的语义边界不一致。与此同时,宏 `tag-match` 会在展开后丢失原生 enum 分支结构,不利于穷尽性、payload arity、类型推断及后端优化;原生 `match` 已具备替代条件。
10
+
11
+ ### 决策
12
+
13
+ 1. Snapshot loader 对 namespace/definition key 同时接受 String 与 Symbol。
14
+ 2. Snapshot formatter 和 writer 只输出 Symbol key,形成“宽读、窄写”的单向迁移。
15
+ 3. String 与 Symbol 归一化后重名时立即报错,不允许 HashMap 静默覆盖。
16
+ 4. runtime、format migration 与 detailed snapshot 读取路径共享同一兼容规则。
17
+ 5. `tag-match` 标记普通 `:deprecated`;`analyze deprecated` 会报告调用,`analyze quality` 会把调用计入 `deprecatedCalls`。新代码及迁移代码使用原生 `match`。
18
+
19
+ ### 兼容性
20
+
21
+ 旧 Snapshot 无需先手工修改即可读取。首次执行 `calcit calcit.cirru edit format` 后会产生标识符 key 的规范化 diff,应单独审阅并提交。新版本写出的 Symbol key 不保证旧 Calcit formatter 可读,因此项目必须先升级工具链再格式化。
22
+
23
+ ## English
24
+
25
+ ### Context
26
+
27
+ Architecture files already represent definition FQNs as Symbols, while the `calcit.cirru` formatter still writes `:files` and `:defs` keys as Strings. Maintaining two persistent representations adds conversion work and blurs the semantic boundary between identifiers and text. The `tag-match` macro also hides native enum branch structure after expansion, limiting exhaustiveness, payload-arity, type-inference, and backend optimization passes; native `match` now covers its intended use.
28
+
29
+ ### Decision
30
+
31
+ 1. Snapshot loaders accept both String and Symbol namespace/definition keys.
32
+ 2. Snapshot formatters and writers emit only Symbol keys, providing a wide-read/narrow-write migration.
33
+ 3. A normalized String/Symbol collision is rejected explicitly instead of being silently overwritten by a HashMap insertion.
34
+ 4. Runtime, format-migration, and detailed-snapshot readers share the same compatibility rule.
35
+ 5. `tag-match` receives the ordinary `:deprecated` tag. `analyze deprecated` reports calls and `analyze quality` includes them in `deprecatedCalls`. New and migrated code uses native `match`.
36
+
37
+ ### Compatibility
38
+
39
+ Legacy Snapshots remain readable without manual edits. The first `calcit calcit.cirru edit format` run produces a canonical identifier-key diff that should be reviewed and committed separately. Because older Calcit formatters are not guaranteed to read newly written Symbol keys, projects must upgrade their toolchain before formatting.
package/RFCs/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # RFC 整理索引
2
2
 
3
- 更新时间:2026-08-26
3
+ 更新时间:2026-08-29
4
4
 
5
5
  ## 目录原则
6
6
 
@@ -50,6 +50,7 @@
50
50
  | `08-21-static-type-system-evolution-roadmap.md` | Draft | 借鉴 Rust/MoonBit 推进 Unknown/Dynamic 分离、穷尽性、局部推断、trait coherence 与框架类型化。 |
51
51
  | `08-23-typed-option-query-ergonomics-rfc.md` | Withdrawn | 生态采用率为零;`get-or` 等六个宏已移除,直接使用查询返回的 `Option` 与 `.unwrap-or`。 |
52
52
  | `08-23-option-result-binding-macros-rfc.md` | Partial | 保留已有 Respo 使用的 `option:let`;移除未采用的 `result:let`,Result 链直接使用 `.and-then`。 |
53
+ | `08-29-snapshot-symbol-keys-and-tag-match-deprecation-rfc.md` | Implemented | Snapshot namespace/definition key 宽读 String/Symbol、窄写 Symbol;`tag-match` 普通废弃并迁移到原生 `match`。 |
53
54
 
54
55
  ## 已执行的清理
55
56
 
package/build.rs CHANGED
@@ -107,6 +107,26 @@ pub struct Snapshot {
107
107
  pub files: HashMap<String, FileInSnapShot>,
108
108
  }
109
109
 
110
+ fn parse_snapshot_identifier_key(value: &Edn, owner: &str) -> Result<String, String> {
111
+ match value {
112
+ Edn::Str(text) | Edn::Symbol(text) if !text.is_empty() => Ok(text.to_string()),
113
+ Edn::Str(_) | Edn::Symbol(_) => Err(format!("{owner}: snapshot identifier key cannot be empty")),
114
+ other => Err(format!(
115
+ "{owner}: snapshot identifier key must be a String or Symbol, got {}",
116
+ format_edn_preview(other)
117
+ )),
118
+ }
119
+ }
120
+
121
+ fn insert_snapshot_identifier<T>(target: &mut HashMap<String, T>, name: String, value: T, owner: &str) -> Result<(), String> {
122
+ if target.insert(name.clone(), value).is_some() {
123
+ return Err(format!(
124
+ "{owner}: duplicate snapshot identifier `{name}` after normalizing String/Symbol keys"
125
+ ));
126
+ }
127
+ Ok(())
128
+ }
129
+
110
130
  fn parse_snapshot_entry(data: Edn) -> Result<SnapshotEntry, String> {
111
131
  let map = data.view_map().map_err(|e| format!("entry must be a map: {e}"))?;
112
132
  let ns_def = |key: &str| match map.get_or_nil(key) {
@@ -516,9 +536,10 @@ fn parse_file_in_snapshot(edn: Edn, file_name: &str) -> Result<FileInSnapShot, S
516
536
  other => return Err(format!("{file_name}: expected `:defs` map, got {}", format_edn_preview(other))),
517
537
  };
518
538
  for (def_key, def_value) in &map.0 {
519
- let name: String = from_edn(def_key.clone()).map_err(|e| format!("{file_name}: invalid def key: {e}"))?;
539
+ let name = parse_snapshot_identifier_key(def_key, &format!("{file_name}/:defs"))?;
520
540
  let owner = format!("{file_name}/{name}");
521
- defs.insert(name, parse_code_entry(def_value.clone(), &owner)?);
541
+ let entry = parse_code_entry(def_value.clone(), &owner)?;
542
+ insert_snapshot_identifier(&mut defs, name, entry, &format!("{file_name}/:defs"))?;
522
543
  }
523
544
  }
524
545
  _ => {}
@@ -535,8 +556,9 @@ fn parse_files(edn: Edn) -> Result<HashMap<String, FileInSnapShot>, String> {
535
556
  Edn::Map(map) => {
536
557
  let mut result = HashMap::with_capacity(map.0.len());
537
558
  for (key, value) in map.0 {
538
- let name: String = from_edn(key).map_err(|e| format!("invalid file key: {e}"))?;
539
- result.insert(name.clone(), parse_file_in_snapshot(value, &name)?);
559
+ let name = parse_snapshot_identifier_key(&key, "snapshot/:files")?;
560
+ let file = parse_file_in_snapshot(value, &name)?;
561
+ insert_snapshot_identifier(&mut result, name, file, "snapshot/:files")?;
540
562
  }
541
563
  Ok(result)
542
564
  }
@@ -0,0 +1,19 @@
1
+ # Async queue byte budget and terminal reserve
2
+
3
+ ## 中文
4
+
5
+ - native async queue 现在可同时限制事件数和累计 payload bytes,避免大量合法但体积较大的事件在 1024 个 event slots 内消耗过多内存。
6
+ - CLI runtime 使用 64 MiB 总字节预算,并为 `Complete`/`Fail` 预留 16 个 event slots 和 64 KiB;普通 Stream/Server emit 不能侵占这些预留资源。
7
+ - event admission 成功后才分配 sequence;event/byte budget 失败都返回稳定的 `QUEUE_FULL`,不会误占 terminal 或 sequence。
8
+ - coalescing 在字节压力下也能替换同一 Stream 的旧 emit,并精确维护 queued-byte accounting;drain/purge 同样回收字节账本。
9
+ - `--trace-ffi` 的 enqueue accepted/rejected 事件现在包含当前 queued events/bytes,便于定位 slow host 或 producer pressure。
10
+ - 修复 async protocol 文档仍描述 legacy Rust callback fallback 的过时内容。
11
+
12
+ ## English
13
+
14
+ - The native async queue can now bound both event count and aggregate payload bytes, preventing a queue of individually valid events from consuming excessive memory.
15
+ - The CLI runtime uses a 64 MiB byte budget and reserves 16 event slots plus 64 KiB for `Complete`/`Fail`; ordinary Stream/Server emits cannot consume those reserves.
16
+ - Event sequences are allocated only after admission. Event-count and byte-budget failures both map to stable `QUEUE_FULL` without claiming a sequence or terminal event.
17
+ - Coalescing can replace an older Stream emit under byte pressure while keeping queued-byte accounting exact; drain and purge release the same accounting.
18
+ - Accepted and rejected `--trace-ffi` enqueue events now include current queued event/byte counters for diagnosing slow hosts and producer pressure.
19
+ - Removed stale async protocol documentation that still described the deleted legacy Rust callback fallback.
@@ -0,0 +1,17 @@
1
+ # 接入异步 runtime 关闭 / Wire async runtime shutdown
2
+
3
+ ## 中文
4
+
5
+ - Calcit CLI 统一安装 Ctrl-C handler,信号线程只设置原子状态,`on-control-c` 回调改由 host thread 串行执行。
6
+ - 调用 async registry `begin_shutdown`,拒绝未完成 response,调用模块 cancel hook,并给 terminal event 保留 2 秒 grace period。
7
+ - grace period 结束后先关闭 host queue,再按 module/method/task/kind/age 输出诊断并强制 purge/release,避免迟到 producer 竞态。
8
+ - Watch loop 与兼容性 `async-sleep` 共享可唤醒的 shutdown signal,避免 Ctrl-C 后长时间挂起。
9
+ - 添加 response rejection、cancel invocation、grace timeout 强制清理测试,并使用真实 fswatch C-safe Stream 验证 cancel→Complete→release,无强制清理。
10
+
11
+ ## English
12
+
13
+ - Install one CLI-owned Ctrl-C handler; the signal thread only updates atomic state, while `on-control-c` callbacks run serially on the host thread.
14
+ - Invoke async-registry `begin_shutdown`, reject open responses, call module cancel hooks, and retain a two-second grace period for terminal events.
15
+ - After the deadline, close the host queue first, then diagnose unfinished tasks by module/method/task/kind/age and force purge/release them without a late-producer race.
16
+ - Let watch loops and compatibility `async-sleep` share a wakeable shutdown signal so Ctrl-C cannot leave the process hanging.
17
+ - Cover response rejection, cancel invocation, and forced cleanup after grace, plus a real fswatch C-safe Stream cancel→Complete→release smoke with no forced cleanup.
@@ -0,0 +1,13 @@
1
+ # 避免 shutdown 重复取消 / Avoid duplicate shutdown cancellation
2
+
3
+ ## 中文
4
+
5
+ - 在 `begin_shutdown` 之前保留 lifecycle snapshot,只向原先处于 `Active` 的 task 调用 cancel hook。
6
+ - 如果 `on-control-c` 或业务逻辑已经把 task 转为 `Closing`,全局 shutdown 只等待 terminal,不会重复调用模块取消。
7
+ - 测试断言已在 Closing 的 task 取消次数保持为零,同时继续受 grace timeout 和强制清理保护。
8
+
9
+ ## English
10
+
11
+ - Preserve the lifecycle snapshot before `begin_shutdown` and invoke cancel hooks only for tasks that were originally `Active`.
12
+ - If `on-control-c` or application logic already moved a task to `Closing`, global shutdown waits for its terminal acknowledgement without calling module cancellation twice.
13
+ - Assert that an already-closing task receives zero additional cancel calls while remaining protected by the grace timeout and forced cleanup.
@@ -0,0 +1,15 @@
1
+ # 完善关闭中断路径 / Complete shutdown interruption paths
2
+
3
+ ## 中文
4
+
5
+ - 按 condvar 谓词协议在持有 wake mutex 时记录 shutdown 并通知,消除检查状态与进入等待之间的丢失唤醒窗口。
6
+ - Native evaluator 低频检查 shutdown 请求,覆盖普通求值与尾递归执行;`on-control-c` 回调在 host thread 运行时临时暂停该检查。
7
+ - Watch 在收到文件事件后重新检查关闭状态;reload 与 codegen 在阶段之间检查,且无 timeout 的 codegen 也由可中断的 worker 执行。
8
+ - Once-mode 运行出错或被中断后不再提前跳过 async runtime 清理,而是先完成有界取消与回收,再返回原始错误。
9
+
10
+ ## English
11
+
12
+ - Record shutdown and notify while holding the condition-variable predicate mutex, closing the lost-wakeup window between checking state and waiting.
13
+ - Poll shutdown cheaply during native evaluation, including tail recursion, while temporarily suppressing interruption for the host-thread `on-control-c` callback.
14
+ - Recheck shutdown after watch events and between reload/codegen phases; timeout-free codegen now also runs behind an interruptible worker boundary.
15
+ - Once-mode failures and interruptions no longer bypass async-runtime cleanup: bounded cancellation and reclamation finish before the original error returns.
@@ -0,0 +1,19 @@
1
+ # 异步 task 队列指标 / Async task queue metrics
2
+
3
+ ## 中文
4
+
5
+ - 在 async queue 同一把 mutex 下维护有界的 per-task 固定元数据索引,覆盖 queued events、queued bytes、oldest age、accepted、coalesced、queue-full、dequeued 与 purged。
6
+ - Enqueue、coalescing、drain 与 purge 的新增指标工作只更新目标 task,不扫描全 registry,也不复制业务 payload;coalescing 延续队列原有的有界定位逻辑。
7
+ - `--trace-ffi` 在 enqueue/reject/cancel/release 展示 task-local 指标;shutdown 额外按 module/method 汇总 active/closing tasks 与 backlog。
8
+ - Task release 时删除指标状态;forced cleanup 诊断保留最终 backlog 与累计值。
9
+ - 单元测试覆盖 coalescing、queue-full、drain、purge、并发 producer 与 shutdown 后指标归零。
10
+ - Fibo release 五次中位数由 251.208ms 变为 250.554ms(-0.26%,无可见回退);真实 fswatch 0.0.9 trace 验证 `Emit → cancel → Complete → release` 且 `forced=0`。
11
+
12
+ ## English
13
+
14
+ - Maintain a bounded fixed-metadata per-task index under the async queue mutex for queued events, queued bytes, oldest age, accepted, coalesced, queue-full, dequeued, and purged counts.
15
+ - The added metric work for enqueue, coalescing, drain, and purge updates only the affected task without scanning the full registry or copying business payloads; coalescing retains the queue's existing bounded lookup.
16
+ - Extend `--trace-ffi` enqueue/reject/cancel/release records with task-local metrics and aggregate active/closing tasks plus backlog by module/method during shutdown.
17
+ - Remove metric state on task release while retaining the final backlog and cumulative counters in forced-cleanup diagnostics.
18
+ - Cover coalescing, queue-full, drain, purge, concurrent producers, and post-shutdown metric removal in tests.
19
+ - The five-run release fibo median changes from 251.208ms to 250.554ms (-0.26%, no visible regression); a real fswatch 0.0.9 trace verifies `Emit → cancel → Complete → release` with `forced=0`.
@@ -0,0 +1,19 @@
1
+ # Native async FFI 指标报告 / Native async FFI metrics report
2
+
3
+ ## 中文
4
+
5
+ - 新增 `--ffi-metrics`,在进程退出时向 stderr 输出唯一一条带 `schemaVersion` 的 JSON,不污染业务 stdout。
6
+ - 已完成 task 的 queue 与 lifecycle 计数折叠进有界的 module/method 聚合;当前 task 则与 live queue snapshot 合并,避免释放后丢失累计证据或长期保留 capability。
7
+ - Host 统一记录 response deadline timeout,以及 cancel 请求、成功与失败;模块内部 retry 与远端耗时明确由模块自身上报。
8
+ - JSON 提供 totals 和稳定排序的 module/method rows,覆盖 active/closing/completed task、backlog、oldest age 与所有 queue outcome。
9
+ - 单元测试覆盖 deadline timeout 与 cooperative/forced shutdown 后聚合;真实 fswatch 0.0.9 Ctrl-C 验证 `completedTasks=1`、cancel request/success 与 Complete dequeue 均为 1、`forced=0`,指标只出现在 stderr。
10
+ - 默认关闭报告时不执行 completed archive 或 outcome 锁更新,且复用已有 task control mutex、不增加每 task 分配;release fibo 五次中位数为 246.086ms,对比上一阶段 250.554ms 无可见回退。
11
+
12
+ ## English
13
+
14
+ - Add `--ffi-metrics`, which emits exactly one schema-versioned JSON record to stderr at process exit without contaminating business stdout.
15
+ - Fold completed-task queue and lifecycle counters into bounded module/method aggregates; merge current tasks with a live queue snapshot so release neither erases cumulative evidence nor retains capabilities indefinitely.
16
+ - Count response deadline timeouts and cancellation requests, successes, and failures in the host, while keeping module-internal retries and remote timings module-owned.
17
+ - Provide totals and deterministically ordered module/method rows for active/closing/completed tasks, backlog, oldest age, and every queue outcome.
18
+ - Cover deadline timeout plus cooperative/forced shutdown aggregation in unit tests; a real fswatch 0.0.9 Ctrl-C run reports `completedTasks=1`, one cancel request/success, one completed dequeue, and `forced=0`, only on stderr.
19
+ - When reporting is disabled, skip completed archives and outcome-lock updates, and reuse the existing task-control mutex without adding a per-task allocation. The five-run release fibo median is 246.086ms versus 250.554ms in the previous stage, with no visible regression.
@@ -0,0 +1,17 @@
1
+ # 类型化 FFI capability / Typed FFI capabilities
2
+
3
+ ## 中文
4
+
5
+ - 在 core 中增加 nominal `FfiTask` 与 `FfiResponse`,把 native async AnyRef 限制在各自 wrapper 的 `Dynamic` raw 字段中。
6
+ - 公开生命周期操作采用方法形式:task 使用 `.cancel` / `.cancel-with`,response 使用 `.resolve` / `.reject`;底层 `&ffi-*` procedure 仅由内部适配函数调用。
7
+ - reason 与 payload 使用方法级泛型 `T`。Calcit trait 禁止方法签名包含 `Dynamic`,因此用户值不会在进入 FFI 编码前丢失静态类型。
8
+ - 两种 capability 使用不同 nominal receiver;错误 receiver 会被 method dispatch 确定性拒绝,错误 raw capability 仍由 native 宿主校验 kind、owner、generation 与 lifecycle。
9
+ - 增加可重放的 architecture scaffold、core 构造测试,以及中英双语协议和使用文档。
10
+
11
+ ## English
12
+
13
+ - Added nominal core `FfiTask` and `FfiResponse` wrappers, confining native async AnyRef values to each wrapper's `Dynamic` raw field.
14
+ - Exposed lifecycle operations as methods: `.cancel` / `.cancel-with` for tasks and `.resolve` / `.reject` for responses. Internal adapters are the only callers of the raw `&ffi-*` procedures.
15
+ - Kept reason and payload values typed with method-level generic `T`. Calcit traits reject `Dynamic` method signatures, so caller-side types survive until FFI encoding.
16
+ - Kept task and response capabilities as distinct nominal receivers. Method dispatch rejects a wrong receiver deterministically, while the native host still validates raw capability kind, owner, generation, and lifecycle.
17
+ - Added a replayable architecture scaffold, core constructor tests, and bilingual protocol and usage documentation.
@@ -0,0 +1,13 @@
1
+ # 发布 0.13.59 / Release 0.13.59
2
+
3
+ ## 中文
4
+
5
+ - 发布 core 类型化 native async FFI capability API。
6
+ - `FfiTask` 提供 `.cancel` / `.cancel-with`,`FfiResponse` 提供 `.resolve` / `.reject`。
7
+ - 下游 native 模块可以从稳定版本开始迁移,不需要引用未发布提交。
8
+
9
+ ## English
10
+
11
+ - Release the typed native async FFI capability API in core.
12
+ - `FfiTask` exposes `.cancel` / `.cancel-with`, while `FfiResponse` exposes `.resolve` / `.reject`.
13
+ - Downstream native modules can migrate from a stable version instead of referencing an unpublished commit.
@@ -0,0 +1,27 @@
1
+ # `tag-match` deprecation and Snapshot Symbol identifier keys
2
+
3
+ ## 中文概要
4
+
5
+ - 将 `calcit.core/tag-match` 标记为普通 `:deprecated`,迁移说明指向原生 `match`;废弃调用继续进入 `analyze deprecated` 与 quality 的 `deprecatedCalls`。
6
+ - 将 core 内 18 个 Option/Result、collection destructuring 与 IO helper 的实际调用迁移到 `match`,并同步迁移相关 examples 和 definition-attached tests;core deprecated 分析归零。
7
+ - 为保留的兼容宏补充 validated pattern-list 类型断言,使专门的 legacy runtime test 不产生类型告警。
8
+ - Snapshot runtime、format migration、detailed snapshot 和 build-script reader 对 `:files` namespace key、`:defs` definition key 同时接受旧 String 与新 Symbol。
9
+ - Snapshot writer 只输出 Symbol identifier key;String/Symbol 归一化重名时明确报错,避免 HashMap 静默覆盖。
10
+ - core Snapshot 经新版 writer 完成 Symbol key 规范化;build.rs 同步支持该格式。
11
+
12
+ ## English summary
13
+
14
+ - Marked `calcit.core/tag-match` with the ordinary `:deprecated` tag and directed migration to native `match`; calls remain part of deprecated analysis and the quality `deprecatedCalls` budget.
15
+ - Migrated 18 real core Option/Result, collection-destructuring, and IO-helper call sites to `match`, including related examples and definition-attached tests; core deprecated analysis now reports zero calls.
16
+ - Added validated pattern-list type assertions inside the retained compatibility macro so its dedicated legacy runtime test remains warning-free.
17
+ - Made runtime, format-migration, detailed-snapshot, and build-script readers accept both legacy String and canonical Symbol namespace/definition keys.
18
+ - Made Snapshot writers emit only Symbol identifier keys and reject normalized String/Symbol collisions instead of silently overwriting entries.
19
+ - Canonicalized the bundled core Snapshot to Symbol keys and updated `build.rs` to consume the format.
20
+
21
+ ## Verification notes
22
+
23
+ - Rust library tests: 580 passed.
24
+ - Core attached tests: 223 passed.
25
+ - `analyze deprecated --deps`: 0 core calls after migration.
26
+ - Temporary legacy-String Snapshot formatted to Symbol keys and reloaded through `query context`.
27
+ - Full Rust, strict clippy, JS/IR/WASM, and Agent interface checks passed before commit/PR.
@@ -0,0 +1,18 @@
1
+ # Async cancellation must purge accepted Emit events
2
+
3
+ When a Calcit callback cancels its own native async task, later Emit events may
4
+ already have been detached from the shared queue into the same host drain
5
+ batch. Moving the registry handle to Closing blocks new producer events, but a
6
+ queue-only purge cannot see that detached batch.
7
+
8
+ The cancellation path now purges events still held by the queue immediately
9
+ after `begin_close`. Drain also treats an Emit observed after the task entered
10
+ Closing as cancellation cleanup: it is discarded without a lifecycle error.
11
+ This is safe because the registry cannot reserve an Emit sequence after
12
+ Closing. Reserved Complete/Fail events remain deliverable for exactly-once
13
+ termination.
14
+
15
+ 当 Calcit callback 在同一轮 drain 中取消 native async task 时,后续 Emit 可能
16
+ 已经随 batch 从共享队列取出,普通 queue purge 无法再看到它们。取消流程现在会在
17
+ `begin_close` 后立即清理队列;drain 遇到 Closing task 的 Emit 时将其视为取消清理,
18
+ 静默 discard,而 terminal 事件仍利用预留容量完成 exactly-once 收尾。
@@ -0,0 +1,17 @@
1
+ # Preserve queued terminal events during cancellation purge
2
+
3
+ Review identified that a task may already have queued Complete/Fail behind an
4
+ earlier Emit when the Emit callback requests cancellation. Purging every event
5
+ would remove that terminal while the registry still remembers its terminal
6
+ claim, leaving the task stuck in Closing.
7
+
8
+ Cancellation now performs a selective Emit-only purge. Queue metadata removes
9
+ the same event sequences and bytes while preserving any queued terminal and
10
+ its exactly-once claim. Tests cover an Emit and Complete already queued before
11
+ cancel, verify only the Emit is purged, and drain the preserved terminal to a
12
+ finished task.
13
+
14
+ review 指出取消发生时 Complete/Fail 可能已经排在较早 Emit 后面。全量 purge 会
15
+ 删除 terminal,却保留 registry 的 terminal claim,导致 task 永久停在 Closing。
16
+ 现在取消只按 sequence 清理 Emit,并同步更新 bytes 与 purged metrics;测试覆盖
17
+ 取消前 Emit 与 Complete 已同时入队,确认 terminal 被保留并正常完成。
@@ -0,0 +1,5 @@
1
+ # Release 0.13.60
2
+
3
+ - Bumped the Rust crate and npm package versions together from `0.13.59` to `0.13.60`.
4
+ - This release includes canonical snapshot identifier keys and corrected async FFI task cancellation: queued `Emit` events are purged without dropping terminal completion or failure events.
5
+ - The async cancellation change prevents stale callback delivery while preserving deterministic task teardown for native modules.
package/lib/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@calcit/procs",
3
- "version": "0.13.58",
3
+ "version": "0.13.60",
4
4
  "main": "./lib/calcit.procs.mjs",
5
5
  "devDependencies": {
6
6
  "@types/node": "^25.7.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@calcit/procs",
3
- "version": "0.13.58",
3
+ "version": "0.13.60",
4
4
  "main": "./lib/calcit.procs.mjs",
5
5
  "devDependencies": {
6
6
  "@types/node": "^25.7.0",