dsh-vibe-math 2.3.5 → 2.3.7

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.
@@ -643,6 +643,10 @@ export function apply(ctx) {
643
643
  decision: source.decision || prev.decision || '',
644
644
  note: source.note || prev.note || '',
645
645
  syncedFrom: objectId,
646
+ // 这条路径**手里就有**权威的对象 id,把它写进记录:`formalObjectIdOf` 在任务表不在内存时
647
+ // (resume 早期)靠它把验证 id 映射回对象,而不是去猜后缀(对象 id 可能自己以 -sN 结尾)。
648
+ // 依旧只在它**确实是对象 id** 时写(调用方也可能把 rId 传进来,例如"归档写在 rId 上")。
649
+ ...(formalObjectIdOfIsOwner(objectId) ? { objectId: objectId } : {}),
646
650
  updatedAt: now(),
647
651
  }))
648
652
  }
@@ -652,12 +656,37 @@ export function apply(ctx) {
652
656
  * 验证 id → 它对应的对象 id(`r-pGate` / `r-pGate-s0` / `r-pGate-pf1` / `r-pGate-rf2` → `pGate`)。
653
657
  * v2 有两套 id 空间,这条映射是**唯一**的一处:门禁的合并查询、提示词取记录、以及回执通道
654
658
  * (`blocked` / `defect` / `used`)都从这里得到对象 id,绝不另造第二套解析规则。
659
+ *
660
+ * **权威来源优先**(两级,都能跨 resume 生效):
661
+ * ① 验证任务自己知道它属于哪个对象(`t.r.pId` / `t.r.qid`);
662
+ * ② 记录里记着的 `objectId`(写记录时由**拿到对象 id 的那条路径**写上)。
663
+ * 只靠字符串后缀解析会有歧义:**对象 id 本身以 `-s1`/`-pf1`/`-rf1` 结尾**时(例如命题 `pAmb-s1` 的
664
+ * 验证 id 是 `r-pAmb-s1`),后缀剥离会把对象截成 `pAmb` —— 另一个对象。后果不是"少一条记录"而是
665
+ * **张冠李戴**:忠实性提示词会打印邻居的证明路径、`defect` 回执会降级邻居的记录并**撤回邻居的归档
666
+ * 证明**,而真正的对象仍然 `passed`(实测复现,见 `formal-verify-v2` 的 ambiguity 用例)。
667
+ * 字符串解析只是前两者都不可用时的兜底。
668
+ *
669
+ * 记录里的 `objectId` 还要求**自洽**(它自己再映射一次还是它自己):否则一次"归档写在 rId 上"的
670
+ * 调用会把 `objectId: 'r-pAlias'` 写进记录,之后(任务表已不在内存时)这个错误的锚点反而会覆盖
671
+ * 正确的后缀解析结果。
655
672
  */
656
673
  function formalObjectIdOf(id) {
657
674
  const t = safeId(String(id == null ? '' : id))
675
+ const task = tasks['verify:' + t]
676
+ if (task && task.r) {
677
+ const owner = String(task.r.pId || task.r.qid || '')
678
+ if (owner) return safeId(owner)
679
+ }
680
+ const rec = formalRecords()[t]
681
+ if (rec && rec.objectId && formalObjectIdOfIsOwner(rec.objectId)) return safeId(rec.objectId)
658
682
  const m = /^r-(.+?)(?:-(?:s\d+|pf\d+|rf\d+))?$/.exec(t)
659
683
  return m ? m[1] : t
660
684
  }
685
+ /** 一个 id 能否作为"对象 id"落进记录:它不能再被解析成别的 id(对象 id 不以 `r-` 开头)。 */
686
+ function formalObjectIdOfIsOwner(v) {
687
+ const s = safeId(String(v == null ? '' : v))
688
+ return !!s && s.indexOf('r-') !== 0
689
+ }
661
690
  /**
662
691
  * 门禁/提示词看到的对象状态 = 合并后的记录,**两个方向都要认**,因为 v2 里归档与验证
663
692
  * 用的是两套 id,而代理两种写法都会用:
@@ -689,11 +718,16 @@ export function apply(ctx) {
689
718
  const t = safeId(String(target == null ? '' : target))
690
719
  if (!t) return []
691
720
  const objectId = formalObjectIdOf(t)
721
+ // 记录里写上权威对象 id:验证 id 的那条记录从此**自带**它属于谁,`formalObjectIdOf` 不必再猜
722
+ // (对象 id 本身可能以 -sN/-pfN/-rfN 结尾,后缀剥离会指向另一个对象)。只有当这个 id **确实
723
+ // 是对象 id**(不以 r- 开头)时才写,免得把 `objectId:'r-pAlias'` 这种错锚点固化进记录。
724
+ const withOwner = (t !== objectId && formalObjectIdOfIsOwner(objectId))
725
+ ? Object.assign({ objectId: objectId }, patch) : patch
692
726
  const written = []
693
727
  const write = async function (k) {
694
728
  if (written.indexOf(k) !== -1) return
695
729
  const prev = formalRecords()[k] || { status: 'none' }
696
- await putFormal(k, Object.assign({}, prev, patch))
730
+ await putFormal(k, Object.assign({}, prev, withOwner))
697
731
  written.push(k)
698
732
  }
699
733
  await write(t) // 回执点名的那个 id(对象 id 或验证 id)
@@ -402,6 +402,17 @@ v2 **没有会话投影**,所以记录与待办一起持久化在 v2 自己的
402
402
  `formalGateRecord` 也用它),**不新造第三套映射**。只写一侧的后果是静默错位:`defect` 只写到验证 id 上,
403
403
  对象记录仍是 `passed`,`formalGateRecord` 从对象侧读回 `passed`、卡片照旧写"Lean 通过"。
404
404
 
405
+ **这条映射不许"猜后缀"**:`r-<对象id>` 的**对象 id 本身可能以 `-sN`/`-pfN`/`-rfN` 结尾**
406
+ (例如命题 `p-ineq-s1` 的验证 id 是 `r-p-ineq-s1`)。若无条件剥离后缀,`formalObjectIdOf` 会把对象
407
+ 截成 `p-ineq` —— **另一个对象**:忠实性提示词会打印邻居的证明路径、`defect` 回执会降级邻居的记录并
408
+ **撤回邻居的归档证明**,而真正的对象仍然 `passed`(实测复现:`formal-verify-v2` 的 ambiguity 用例在修
409
+ 前 6 条断言变红,其中包含"邻居的证明被删除")。因此 `formalObjectIdOf` 按**权威度**取来源:
410
+
411
+ 1. 验证任务自己的所有者(`tasks['verify:'+rId].r.pId | qid`)——框架生成 rId 时就知道对象是谁;
412
+ 2. 记录里的 `objectId`(`syncVerificationTarget` 与 `putFormalBothIds` 在**手里有对象 id** 时写入,
413
+ 因此跨 resume、任务表还没重建时也正确);
414
+ 3. 只有在两者都不可用时,才回退到字符串后缀解析。
415
+
405
416
  #### 9.4 提示词的三条硬要求
406
417
 
407
418
  - **工具名一律写全称**:注入文本里只出现注册名 `vibe_math_lean_run` / `vibe_math_lean_archive` /