Skip to content

Commit 76207e0

Browse files
authored
Merge pull request #271 from Integration-Automation/feat/json-patch
Add JSON Pointer, Patch and Merge Patch
2 parents 8458e86 + 7625bbe commit 76207e0

15 files changed

Lines changed: 735 additions & 2 deletions

File tree

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
## Table of Contents
1515

16+
- [What's new (2026-06-21) — JSON Pointer, Patch & Merge Patch](#whats-new-2026-06-21--json-pointer-patch--merge-patch)
1617
- [What's new (2026-06-21) — Client-Side Rate Limiting](#whats-new-2026-06-21--client-side-rate-limiting)
1718
- [What's new (2026-06-21) — JSON Web Tokens (JWT)](#whats-new-2026-06-21--json-web-tokens-jwt)
1819
- [What's new (2026-06-21) — License Policy Gate](#whats-new-2026-06-21--license-policy-gate)
@@ -115,6 +116,12 @@
115116

116117
---
117118

119+
## What's new (2026-06-21) — JSON Pointer, Patch & Merge Patch
120+
121+
Address, diff and patch JSON. Full reference: [`docs/source/Eng/doc/new_features/v63_features_doc.rst`](docs/source/Eng/doc/new_features/v63_features_doc.rst).
122+
123+
- **`resolve_pointer` / `make_patch` / `apply_patch` / `merge_patch` / `make_merge_patch`** (`AC_resolve_pointer`, `AC_apply_json_patch`, `AC_make_json_patch`, `AC_merge_patch`): `jsonpath` is read-only and `approval` compares whole artifacts — nothing could address one location, compute a structured delta, or apply a partial update. This adds the three IETF primitives — JSON Pointer (RFC 6901), JSON Patch (RFC 6902, all six ops, **atomic** apply), and JSON Merge Patch (RFC 7386, `null` deletes) — for config-drift detection, partial updates, HTTP PATCH bodies, and golden-master deltas. Pure-stdlib `json`+`copy`, validated against the RFC test vectors.
124+
118125
## What's new (2026-06-21) — Client-Side Rate Limiting
119126

120127
Stay under API quotas. Full reference: [`docs/source/Eng/doc/new_features/v62_features_doc.rst`](docs/source/Eng/doc/new_features/v62_features_doc.rst).

README/README_zh-CN.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
## 目录
1414

15+
- [本次更新 (2026-06-21) — JSON Pointer、Patch 与 Merge Patch](#本次更新-2026-06-21--json-pointerpatch-与-merge-patch)
1516
- [本次更新 (2026-06-21) — 客户端速率限制](#本次更新-2026-06-21--客户端速率限制)
1617
- [本次更新 (2026-06-21) — JSON Web Token(JWT)](#本次更新-2026-06-21--json-web-tokenjwt)
1718
- [本次更新 (2026-06-21) — 许可证策略闸门](#本次更新-2026-06-21--许可证策略闸门)
@@ -114,6 +115,12 @@
114115

115116
---
116117

118+
## 本次更新 (2026-06-21) — JSON Pointer、Patch 与 Merge Patch
119+
120+
定址、取差异并修补 JSON。完整参考:[`docs/source/Zh/doc/new_features/v63_features_doc.rst`](../docs/source/Zh/doc/new_features/v63_features_doc.rst)
121+
122+
- **`resolve_pointer` / `make_patch` / `apply_patch` / `merge_patch` / `make_merge_patch`**(`AC_resolve_pointer``AC_apply_json_patch``AC_make_json_patch``AC_merge_patch`):`jsonpath` 是只读的,`approval` 比较整份产物 —— 没有任何东西能定址单一位置、计算结构化差异或套用部分更新。本功能补上三个 IETF 原语 —— JSON Pointer(RFC 6901)、JSON Patch(RFC 6902,全六种操作,**原子**套用)、JSON Merge Patch(RFC 7386,`null` 删除)—— 适用于配置漂移检测、部分更新、HTTP PATCH 内容与 golden-master 差异。纯标准库 `json`+`copy`,以 RFC 测试向量验证。
123+
117124
## 本次更新 (2026-06-21) — 客户端速率限制
118125

119126
守在 API 配额之内。完整参考:[`docs/source/Zh/doc/new_features/v62_features_doc.rst`](../docs/source/Zh/doc/new_features/v62_features_doc.rst)

README/README_zh-TW.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
## 目錄
1414

15+
- [本次更新 (2026-06-21) — JSON Pointer、Patch 與 Merge Patch](#本次更新-2026-06-21--json-pointerpatch-與-merge-patch)
1516
- [本次更新 (2026-06-21) — 用戶端速率限制](#本次更新-2026-06-21--用戶端速率限制)
1617
- [本次更新 (2026-06-21) — JSON Web Token(JWT)](#本次更新-2026-06-21--json-web-tokenjwt)
1718
- [本次更新 (2026-06-21) — 授權政策閘門](#本次更新-2026-06-21--授權政策閘門)
@@ -114,6 +115,12 @@
114115

115116
---
116117

118+
## 本次更新 (2026-06-21) — JSON Pointer、Patch 與 Merge Patch
119+
120+
定址、取差異並修補 JSON。完整參考:[`docs/source/Zh/doc/new_features/v63_features_doc.rst`](../docs/source/Zh/doc/new_features/v63_features_doc.rst)
121+
122+
- **`resolve_pointer` / `make_patch` / `apply_patch` / `merge_patch` / `make_merge_patch`**(`AC_resolve_pointer``AC_apply_json_patch``AC_make_json_patch``AC_merge_patch`):`jsonpath` 是唯讀的,`approval` 比較整份產物 —— 沒有任何東西能定址單一位置、計算結構化差異或套用部分更新。本功能補上三個 IETF 原語 —— JSON Pointer(RFC 6901)、JSON Patch(RFC 6902,全六種操作,**原子**套用)、JSON Merge Patch(RFC 7386,`null` 刪除)—— 適用於設定漂移偵測、部分更新、HTTP PATCH 內容與 golden-master 差異。純標準函式庫 `json`+`copy`,以 RFC 測試向量驗證。
123+
117124
## 本次更新 (2026-06-21) — 用戶端速率限制
118125

119126
守在 API 配額之內。完整參考:[`docs/source/Zh/doc/new_features/v62_features_doc.rst`](../docs/source/Zh/doc/new_features/v62_features_doc.rst)
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
JSON Pointer, Patch & Merge Patch
2+
=================================
3+
4+
``jsonpath`` queries are read-only and ``approval`` compares whole artifacts by
5+
equality, but nothing could address a single location, compute a structured
6+
*delta*, or apply a partial update to a JSON document. This adds the three IETF
7+
primitives that fill that gap:
8+
9+
* **RFC 6901 JSON Pointer** — address one location (``/a/b/0``).
10+
* **RFC 6902 JSON Patch** — an ordered op list
11+
(add/remove/replace/move/copy/test); plus ``make_patch`` to diff two docs.
12+
* **RFC 7386 JSON Merge Patch** — a recursive merge where ``null`` deletes.
13+
14+
Useful for config-drift detection, partial updates in flows, HTTP PATCH bodies,
15+
and reporting golden-master deltas. Pure standard library (``json`` + ``copy``);
16+
fully deterministic; imports no ``PySide6``.
17+
18+
Headless API
19+
------------
20+
21+
.. code-block:: python
22+
23+
from je_auto_control import (
24+
resolve_pointer, make_patch, apply_patch, merge_patch,
25+
make_merge_patch, set_pointer, remove_pointer)
26+
27+
doc = {"user": {"name": "Jo", "tags": ["a", "b"]}}
28+
29+
resolve_pointer(doc, "/user/tags/0") # "a"
30+
31+
patch = make_patch(doc, {"user": {"name": "Joe", "tags": ["a"]}})
32+
# [{"op": "replace", "path": "/user/name", "value": "Joe"},
33+
# {"op": "remove", "path": "/user/tags/1"}]
34+
apply_patch(doc, patch) # the updated document
35+
36+
merge_patch({"a": 1, "b": 2}, {"b": None, "c": 3}) # {"a": 1, "c": 3}
37+
38+
``apply_patch`` is **atomic** — it applies to a copy and only returns on full
39+
success, so a failing ``test`` op leaves the original untouched. The six ops
40+
follow RFC 6902 exactly (``add`` inserts into arrays, ``test`` does deep
41+
equality keeping ``true`` distinct from ``1``, ``move`` rejects moving a value
42+
into its own child). ``set_pointer`` / ``remove_pointer`` are pure
43+
single-location convenience helpers. ``merge_patch`` follows RFC 7386 (a
44+
``null`` value deletes the key; a non-object patch replaces wholesale).
45+
46+
Executor commands
47+
-----------------
48+
49+
``AC_resolve_pointer`` (``{value}``), ``AC_apply_json_patch`` (``{result}``),
50+
``AC_make_json_patch`` (``{patch}``) and ``AC_merge_patch`` (``{result}``) take
51+
their JSON inputs as objects or JSON strings. Each is also exposed as an MCP
52+
tool (``ac_resolve_pointer`` / ``ac_apply_json_patch`` / ``ac_make_json_patch``
53+
/ ``ac_merge_patch``) and as a Script Builder command under **Data**.

docs/source/Eng/eng_index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ Comprehensive guides for all AutoControl features.
8585
doc/new_features/v60_features_doc
8686
doc/new_features/v61_features_doc
8787
doc/new_features/v62_features_doc
88+
doc/new_features/v63_features_doc
8889
doc/ocr_backends/ocr_backends_doc
8990
doc/observability/observability_doc
9091
doc/operations_layer/operations_layer_doc
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
JSON Pointer、Patch 與 Merge Patch
2+
==================================
3+
4+
``jsonpath`` 查詢是唯讀的,而 ``approval`` 以相等性比較整份產物,但沒有任何東西能定址單一位置、
5+
計算結構化*差異*,或對 JSON 文件套用部分更新。本功能補上填補此缺口的三個 IETF 原語:
6+
7+
* **RFC 6901 JSON Pointer** —— 定址單一位置(``/a/b/0``)。
8+
* **RFC 6902 JSON Patch** —— 有序的操作清單(add/remove/replace/move/copy/test);另含
9+
``make_patch`` 以對兩份文件取差異。
10+
* **RFC 7386 JSON Merge Patch** —— 遞迴合併,其中 ``null`` 代表刪除。
11+
12+
適用於設定漂移偵測、流程中的部分更新、HTTP PATCH 內容,以及回報 golden-master 差異。純標準
13+
函式庫(``json`` + ``copy``);完全具決定性;不匯入 ``PySide6``。
14+
15+
無頭 API
16+
--------
17+
18+
.. code-block:: python
19+
20+
from je_auto_control import (
21+
resolve_pointer, make_patch, apply_patch, merge_patch,
22+
make_merge_patch, set_pointer, remove_pointer)
23+
24+
doc = {"user": {"name": "Jo", "tags": ["a", "b"]}}
25+
26+
resolve_pointer(doc, "/user/tags/0") # "a"
27+
28+
patch = make_patch(doc, {"user": {"name": "Joe", "tags": ["a"]}})
29+
# [{"op": "replace", "path": "/user/name", "value": "Joe"},
30+
# {"op": "remove", "path": "/user/tags/1"}]
31+
apply_patch(doc, patch) # 更新後的文件
32+
33+
merge_patch({"a": 1, "b": 2}, {"b": None, "c": 3}) # {"a": 1, "c": 3}
34+
35+
``apply_patch`` 是**原子的** —— 它套用在副本上,只有完全成功才回傳,因此失敗的 ``test`` 操作
36+
會讓原始文件保持不變。六個操作完全遵循 RFC 6902(``add`` 會插入陣列、``test`` 做深度相等且讓
37+
``true`` 與 ``1`` 相異、``move`` 拒絕把值移入自身子節點)。``set_pointer`` / ``remove_pointer``
38+
是純粹的單一位置便利函式。``merge_patch`` 遵循 RFC 7386(``null`` 值刪除該鍵;非物件的 patch
39+
整體取代)。
40+
41+
執行器命令
42+
----------
43+
44+
``AC_resolve_pointer``(``{value}``)、``AC_apply_json_patch``(``{result}``)、
45+
``AC_make_json_patch``(``{patch}``)與 ``AC_merge_patch``(``{result}``)接受物件或 JSON 字串
46+
作為輸入。每個亦以 MCP 工具(``ac_resolve_pointer`` / ``ac_apply_json_patch`` /
47+
``ac_make_json_patch`` / ``ac_merge_patch``)以及 Script Builder 中 **Data** 分類下的命令提供。

docs/source/Zh/zh_index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ AutoControl 所有功能的完整使用指南。
8585
doc/new_features/v60_features_doc
8686
doc/new_features/v61_features_doc
8787
doc/new_features/v62_features_doc
88+
doc/new_features/v63_features_doc
8889
doc/ocr_backends/ocr_backends_doc
8990
doc/observability/observability_doc
9091
doc/operations_layer/operations_layer_doc

je_auto_control/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,11 @@
324324
from je_auto_control.utils.rate_limit import (
325325
SlidingWindowLimiter, TokenBucket, throttle,
326326
)
327+
# JSON Pointer / Patch / Merge Patch (RFC 6901 / 6902 / 7386)
328+
from je_auto_control.utils.json_patch import (
329+
PatchError, PatchTestFailed, apply_patch, make_merge_patch, make_patch,
330+
merge_patch, remove_pointer, resolve_pointer, set_pointer,
331+
)
327332
# Background popup/interrupt watchdog (unattended automation)
328333
from je_auto_control.utils.watchdog import (
329334
PopupWatchdog, WatchdogRule, default_popup_watchdog,
@@ -803,6 +808,9 @@ def start_autocontrol_gui(*args, **kwargs):
803808
"ClaimsPolicy", "ExpiredTokenError", "InvalidSignatureError", "JwtError",
804809
"decode_jwt", "encode_jwt",
805810
"SlidingWindowLimiter", "TokenBucket", "throttle",
811+
"PatchError", "PatchTestFailed", "apply_patch", "make_merge_patch",
812+
"make_patch", "merge_patch", "remove_pointer", "resolve_pointer",
813+
"set_pointer",
806814
# MCP server
807815
"AuditLogger", "HttpMCPServer", "MCPContent", "MCPPrompt",
808816
"MCPPromptArgument", "MCPResource", "MCPServer", "MCPTool",

je_auto_control/gui/script_builder/command_schema.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1155,6 +1155,40 @@ def _add_misc_specs(specs: List[CommandSpec]) -> None:
11551155
),
11561156
description="Validate JSON against a JSON Schema; returns {ok, errors}.",
11571157
))
1158+
specs.append(CommandSpec(
1159+
"AC_resolve_pointer", "Data", "JSON Pointer: Resolve",
1160+
fields=(
1161+
FieldSpec("doc", FieldType.STRING, placeholder='{"a": {"b": [1, 2]}}'),
1162+
FieldSpec("pointer", FieldType.STRING, placeholder="/a/b/0"),
1163+
),
1164+
description="Resolve an RFC 6901 JSON Pointer; returns {value}.",
1165+
))
1166+
specs.append(CommandSpec(
1167+
"AC_apply_json_patch", "Data", "JSON Patch: Apply",
1168+
fields=(
1169+
FieldSpec("doc", FieldType.STRING, placeholder='{"a": 1}'),
1170+
FieldSpec("patch", FieldType.STRING,
1171+
placeholder='[{"op": "add", "path": "/b", "value": 2}]'),
1172+
),
1173+
description="Apply an RFC 6902 JSON Patch; returns {result}.",
1174+
))
1175+
specs.append(CommandSpec(
1176+
"AC_make_json_patch", "Data", "JSON Patch: Diff",
1177+
fields=(
1178+
FieldSpec("old", FieldType.STRING, placeholder='{"a": 1}'),
1179+
FieldSpec("new", FieldType.STRING, placeholder='{"a": 2}'),
1180+
),
1181+
description="Compute an RFC 6902 patch from old to new; returns {patch}.",
1182+
))
1183+
specs.append(CommandSpec(
1184+
"AC_merge_patch", "Data", "JSON Merge Patch: Apply",
1185+
fields=(
1186+
FieldSpec("doc", FieldType.STRING, placeholder='{"a": 1, "b": 2}'),
1187+
FieldSpec("patch", FieldType.STRING,
1188+
placeholder='{"b": null, "c": 3}'),
1189+
),
1190+
description="Apply an RFC 7386 merge patch (null deletes); returns {result}.",
1191+
))
11581192
specs.append(CommandSpec(
11591193
"AC_scan_vulns", "Security", "Scan Dependencies for Vulnerabilities",
11601194
fields=(

je_auto_control/utils/executor/action_executor.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2928,6 +2928,48 @@ def _rate_limit(name: str, rate: float = 1.0, capacity: float = 1.0,
29282928
"wait": round(bucket.time_until_available(float(n)), 4)}
29292929

29302930

2931+
def _resolve_pointer(doc: Any, pointer: str) -> Dict[str, Any]:
2932+
"""Adapter: resolve a JSON Pointer in doc (a dict/list or JSON string)."""
2933+
import json
2934+
from je_auto_control.utils.json_patch import resolve_pointer
2935+
if isinstance(doc, str):
2936+
doc = json.loads(doc)
2937+
return {"value": resolve_pointer(doc, pointer)}
2938+
2939+
2940+
def _apply_json_patch(doc: Any, patch: Any) -> Dict[str, Any]:
2941+
"""Adapter: apply an RFC 6902 JSON Patch (each a list/object or JSON str)."""
2942+
import json
2943+
from je_auto_control.utils.json_patch import apply_patch
2944+
if isinstance(doc, str):
2945+
doc = json.loads(doc)
2946+
if isinstance(patch, str):
2947+
patch = json.loads(patch)
2948+
return {"result": apply_patch(doc, patch)}
2949+
2950+
2951+
def _make_json_patch(old: Any, new: Any) -> Dict[str, Any]:
2952+
"""Adapter: compute an RFC 6902 patch turning old into new."""
2953+
import json
2954+
from je_auto_control.utils.json_patch import make_patch
2955+
if isinstance(old, str):
2956+
old = json.loads(old)
2957+
if isinstance(new, str):
2958+
new = json.loads(new)
2959+
return {"patch": make_patch(old, new)}
2960+
2961+
2962+
def _merge_patch(doc: Any, patch: Any) -> Dict[str, Any]:
2963+
"""Adapter: apply an RFC 7386 JSON Merge Patch (null deletes)."""
2964+
import json
2965+
from je_auto_control.utils.json_patch import merge_patch
2966+
if isinstance(doc, str):
2967+
doc = json.loads(doc)
2968+
if isinstance(patch, str):
2969+
patch = json.loads(patch)
2970+
return {"result": merge_patch(doc, patch)}
2971+
2972+
29312973
def _jwt_encode(claims: Any, key: str, alg: str = "HS256") -> Dict[str, Any]:
29322974
"""Adapter: sign a compact JWT from claims (a dict or JSON string)."""
29332975
import json
@@ -3739,6 +3781,10 @@ def __init__(self):
37393781
"AC_jwt_encode": _jwt_encode,
37403782
"AC_jwt_decode": _jwt_decode,
37413783
"AC_rate_limit": _rate_limit,
3784+
"AC_resolve_pointer": _resolve_pointer,
3785+
"AC_apply_json_patch": _apply_json_patch,
3786+
"AC_make_json_patch": _make_json_patch,
3787+
"AC_merge_patch": _merge_patch,
37423788
"AC_generate_sop": _generate_sop,
37433789
"AC_tween_drag": _tween_drag,
37443790
"AC_list_plugins": _list_plugins,

0 commit comments

Comments
 (0)