@xdxer/dingtalk-agent 0.1.5-beta.10 → 0.1.5-beta.12

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.
@@ -12,6 +12,7 @@ shell proxy cannot break calls to intranet/pre endpoints.
12
12
  """
13
13
 
14
14
  import argparse
15
+ import hashlib
15
16
  import json
16
17
  import os
17
18
  import re
@@ -629,14 +630,67 @@ def load_predicate_registry(manifest_path: str) -> Dict[str, Any]:
629
630
  return predicates
630
631
 
631
632
 
633
+ PREDICATE_REGISTRY_SCHEMA = "dingtalk-agent/predicate-registry@1"
634
+ PREDICATE_REGISTRY_KEYS = ["$schema", "hash", "predicates", "project", "schedules"]
635
+
636
+
637
+ def _stable_json(value: Any) -> str:
638
+ """src/events.ts stableStringify 的镜像:键排序、无空格。改一边必须改另一边——
639
+ 两套序列化分叉就等于两个不同的 hash,投影的完整性校验会互相判错。"""
640
+ return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
641
+
642
+
643
+ def registry_hash(projection: Dict[str, Any]) -> str:
644
+ unsigned = {k: v for k, v in projection.items() if k != "hash"}
645
+ return hashlib.sha256(_stable_json(unsigned).encode("utf-8")).hexdigest()
646
+
647
+
648
+ def load_delivered_registry(path: str) -> Dict[str, Any]:
649
+ """读 deploy 交付进 runtime 的受管 registry 投影(issue #46)。
650
+
651
+ 这是运行侧真正能拿到的那份文件——manifest 不随 skill-push 进入 runtime,所以运行拍
652
+ 读的是它。structure + 完整性 hash 都校验:文件缺失/被改/被截断一律非零退出,判据
653
+ 解析不出来时这一拍不得使用完成措辞。hash 是无密钥完整性校验,不证签发方。"""
654
+ try:
655
+ with open(path, "r", encoding="utf-8") as f:
656
+ projection = json.load(f)
657
+ except OSError as e:
658
+ fail(f"registry 投影读不到: {path} ({e})——判据未交付,本拍不得声称完成")
659
+ except json.JSONDecodeError as e:
660
+ fail(f"registry 投影不是合法 JSON: {path} ({e})")
661
+ if not isinstance(projection, dict):
662
+ fail("registry 投影必须是 JSON object")
663
+ if sorted(projection) != PREDICATE_REGISTRY_KEYS:
664
+ fail(f"registry 投影字段集必须恰为 {'/'.join(PREDICATE_REGISTRY_KEYS)}")
665
+ if projection.get("$schema") != PREDICATE_REGISTRY_SCHEMA:
666
+ fail(f"registry 投影 schema 非法: {projection.get('$schema')!r}")
667
+ predicates = projection.get("predicates")
668
+ if not isinstance(predicates, dict):
669
+ fail("registry 投影 predicates 必须是 object")
670
+ actual = registry_hash(projection)
671
+ if projection.get("hash") != actual:
672
+ fail(
673
+ "registry 投影 hash 与内容不一致(declared="
674
+ f"{str(projection.get('hash'))[:12]}, actual={actual[:12]})——"
675
+ "交付的判据被改过或截断,fail-closed"
676
+ )
677
+ return projection
678
+
679
+
632
680
  LOCAL_COMMANDS = {"predicate-resolve"}
633
681
 
634
682
 
635
683
  def cmd_predicate_resolve(ctx: Optional[Ctx], args: argparse.Namespace) -> None:
636
- """运行时路径:拿到 autopilot description(或直接给 id)→ 在同一份 manifest
637
- 注册表里解析出判据正文。未注册 / marker 缺失 / 冲突都非零退出——判据解析
684
+ """判据解析:从声明侧 manifest(operator)或交付进 runtime 的受管投影(运行拍)
685
+ 解析出判据正文。未注册 / marker 缺失 / 冲突 / 投影 hash 对不上都非零退出——判据解析
638
686
  失败时这一拍不能假装知道怎么算完成。"""
639
- predicates = load_predicate_registry(args.manifest)
687
+ if args.registry:
688
+ projection = load_delivered_registry(args.registry)
689
+ predicates = projection["predicates"]
690
+ resolved_from_registry = projection["hash"]
691
+ else:
692
+ predicates = load_predicate_registry(args.manifest)
693
+ resolved_from_registry = ""
640
694
  if args.id:
641
695
  pid = args.id
642
696
  source = "flag"
@@ -652,8 +706,13 @@ def cmd_predicate_resolve(ctx: Optional[Ctx], args: argparse.Namespace) -> None:
652
706
  source = "description-marker"
653
707
  entry = predicates.get(pid)
654
708
  if entry is None:
655
- fail(f"predicate:{pid} 未在 manifest 注册表登记(registered={sorted(predicates)})")
656
- out({"id": pid, "resolvedFrom": source, "predicate": entry})
709
+ where = "交付的 registry 投影" if args.registry else "manifest 注册表"
710
+ fail(f"predicate:{pid} 未在{where}登记(registered={sorted(predicates)})")
711
+ out({
712
+ "id": pid, "resolvedFrom": source, "predicate": entry,
713
+ "source": "delivered-registry" if args.registry else "project-manifest",
714
+ **({"registryHash": resolved_from_registry} if resolved_from_registry else {}),
715
+ })
657
716
 
658
717
 
659
718
  def _annotate_predicate_ref(autopilot: Any) -> Any:
@@ -1292,10 +1351,17 @@ def build_parser() -> argparse.ArgumentParser:
1292
1351
  p = cmd(
1293
1352
  "predicate-resolve",
1294
1353
  cmd_predicate_resolve,
1295
- "Resolve a completion predicate against the project manifest registry "
1296
- "(runtime reads the SAME dingtalk-agent.json the declaration lives in)",
1354
+ "Resolve a completion predicate from the delivered runtime registry projection "
1355
+ "(--registry, what a schedule run actually has) or from the declaring manifest "
1356
+ "(--manifest, operator-side against the local checkout)",
1357
+ )
1358
+ where_group = p.add_mutually_exclusive_group(required=True)
1359
+ where_group.add_argument("--manifest", help="path to dingtalk-agent.json (declaration side)")
1360
+ where_group.add_argument(
1361
+ "--registry",
1362
+ help="path to the delivered predicate-registry.json inside the skill tree "
1363
+ "(runtime side; schema + integrity hash are verified)",
1297
1364
  )
1298
- p.add_argument("--manifest", required=True, help="path to dingtalk-agent.json")
1299
1365
  src_group = p.add_mutually_exclusive_group(required=True)
1300
1366
  src_group.add_argument("--id", help="predicate id (skip marker parsing)")
1301
1367
  src_group.add_argument("--description", help="autopilot description; the [dta-completion-predicate] marker is parsed out")