Skip to content

Commit be6af5f

Browse files
committed
Add maker-checker approval gate for high-risk actions
1 parent ddd11a1 commit be6af5f

15 files changed

Lines changed: 456 additions & 1 deletion

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-19) — Maker-Checker Approval Gate](#whats-new-2026-06-19--maker-checker-approval-gate)
1617
- [What's new (2026-06-19) — Plugin SDK](#whats-new-2026-06-19--plugin-sdk)
1718
- [What's new (2026-06-19) — MCP Structured Output](#whats-new-2026-06-19--mcp-structured-output)
1819
- [What's new (2026-06-19) — Tweened Drag](#whats-new-2026-06-19--tweened-drag)
@@ -84,6 +85,12 @@
8485

8586
---
8687

88+
## What's new (2026-06-19) — Maker-Checker Approval Gate
89+
90+
Segregation of duties for high-risk steps. Full reference: [`docs/source/Eng/doc/new_features/v32_features_doc.rst`](docs/source/Eng/doc/new_features/v32_features_doc.rst).
91+
92+
- **`ApprovalGate`** (`AC_approval_request` / `AC_approval_approve` / `AC_approval_reject` / `AC_approval_status`, `ac_*`): a *maker* files a high-risk action and gets a token; a *checker* — required to be a **different** principal — approves or rejects it; the action proceeds only once `is_approved` is true. State is an optional shared JSON file so the dispatcher and the human approver can run as separate processes. Pure-stdlib, SOC2-style four-eyes control.
93+
8794
## What's new (2026-06-19) — Plugin SDK
8895

8996
Third-party `AC_*` commands via entry points. Full reference: [`docs/source/Eng/doc/new_features/v31_features_doc.rst`](docs/source/Eng/doc/new_features/v31_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-19) — Maker-Checker 审批闸门](#本次更新-2026-06-19--maker-checker-审批闸门)
1516
- [本次更新 (2026-06-19) — Plugin SDK](#本次更新-2026-06-19--plugin-sdk)
1617
- [本次更新 (2026-06-19) — MCP 结构化输出](#本次更新-2026-06-19--mcp-结构化输出)
1718
- [本次更新 (2026-06-19) — 缓动拖拽](#本次更新-2026-06-19--缓动拖拽)
@@ -83,6 +84,12 @@
8384

8485
---
8586

87+
## 本次更新 (2026-06-19) — Maker-Checker 审批闸门
88+
89+
高风险步骤的职责分离。完整参考:[`docs/source/Zh/doc/new_features/v32_features_doc.rst`](../docs/source/Zh/doc/new_features/v32_features_doc.rst)。
90+
91+
- **`ApprovalGate`**(`AC_approval_request` / `AC_approval_approve` / `AC_approval_reject` / `AC_approval_status`、`ac_*`):由 *maker* 提出高风险动作并取得 token;*checker*(必须为**不同**主体)核准或驳回;只有在 `is_approved` 为真后动作才继续。状态为选用的共享 JSON 文件,让派发器与人工审批者可分属不同进程。纯标准库,SOC2 式四眼原则控制。
92+
8693
## 本次更新 (2026-06-19) — Plugin SDK
8794

8895
通过 entry points 注册第三方 `AC_*` 指令。完整参考:[`docs/source/Zh/doc/new_features/v31_features_doc.rst`](../docs/source/Zh/doc/new_features/v31_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-19) — Maker-Checker 審批閘門](#本次更新-2026-06-19--maker-checker-審批閘門)
1516
- [本次更新 (2026-06-19) — Plugin SDK](#本次更新-2026-06-19--plugin-sdk)
1617
- [本次更新 (2026-06-19) — MCP 結構化輸出](#本次更新-2026-06-19--mcp-結構化輸出)
1718
- [本次更新 (2026-06-19) — 緩動拖曳](#本次更新-2026-06-19--緩動拖曳)
@@ -83,6 +84,12 @@
8384

8485
---
8586

87+
## 本次更新 (2026-06-19) — Maker-Checker 審批閘門
88+
89+
高風險步驟的職責分離。完整參考:[`docs/source/Zh/doc/new_features/v32_features_doc.rst`](../docs/source/Zh/doc/new_features/v32_features_doc.rst)。
90+
91+
- **`ApprovalGate`**(`AC_approval_request` / `AC_approval_approve` / `AC_approval_reject` / `AC_approval_status`、`ac_*`):由 *maker* 提出高風險動作並取得 token;*checker*(必須為**不同**主體)核准或駁回;只有在 `is_approved` 為真後動作才繼續。狀態為選用的共用 JSON 檔,讓派發器與人工審批者可分屬不同程序。純標準函式庫,SOC2 式四眼原則控制。
92+
8693
## 本次更新 (2026-06-19) — Plugin SDK
8794

8895
透過 entry points 註冊第三方 `AC_*` 指令。完整參考:[`docs/source/Zh/doc/new_features/v31_features_doc.rst`](../docs/source/Zh/doc/new_features/v31_features_doc.rst)。
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
Maker-Checker Approval Gate
2+
===========================
3+
4+
Some automation steps are too consequential to fire on one party's say-so —
5+
deleting production data, wiring money, promoting a release. ``ApprovalGate``
6+
adds a **segregation of duties** control: a *maker* files a request and gets a
7+
token; a *checker*, who must be a **different** principal, approves or rejects
8+
it; the action proceeds only once the token is approved.
9+
10+
State is an optional JSON file, so the maker (e.g. a CI dispatcher) and the
11+
checker (e.g. a human approver) can run as separate processes. The module is
12+
pure standard library and imports no ``PySide6``; tokens use :mod:`secrets`.
13+
14+
Headless API
15+
------------
16+
17+
.. code-block:: python
18+
19+
from je_auto_control import ApprovalGate
20+
21+
gate = ApprovalGate("approvals.json") # shared across processes
22+
token = gate.request("delete prod table", requester="alice")
23+
24+
# Self-approval is refused — the checker must differ from the maker.
25+
gate.approve(token, "alice") # -> False
26+
gate.approve(token, "bob") # -> True
27+
28+
if gate.is_approved(token):
29+
run_high_risk_action()
30+
31+
``reject(token, approver)`` blocks an action; a request that has already been
32+
decided cannot be re-decided. ``status(token)`` returns
33+
``pending`` / ``approved`` / ``rejected`` (or ``None`` for an unknown token),
34+
``get(token)`` returns the full record, and ``pending()`` lists every request
35+
still awaiting a decision.
36+
37+
Executor commands
38+
-----------------
39+
40+
================================ ===================================================
41+
Command Effect
42+
================================ ===================================================
43+
``AC_approval_request`` File a request for ``action``; returns ``{token}``.
44+
``AC_approval_approve`` Approve ``token`` as ``approver``; ``{approved}``.
45+
``AC_approval_reject`` Reject ``token`` as ``approver``; ``{rejected}``.
46+
``AC_approval_status`` Return ``{status, approved}`` to gate an action.
47+
================================ ===================================================
48+
49+
Each command accepts an optional ``db`` path so a flow can persist requests to
50+
a shared JSON file. The same operations are exposed as MCP tools
51+
(``ac_approval_request`` / ``ac_approval_approve`` / ``ac_approval_reject`` /
52+
``ac_approval_status``) and as Script Builder commands under **Tools**.

‎docs/source/Eng/eng_index.rst‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ Comprehensive guides for all AutoControl features.
5454
doc/new_features/v29_features_doc
5555
doc/new_features/v30_features_doc
5656
doc/new_features/v31_features_doc
57+
doc/new_features/v32_features_doc
5758
doc/ocr_backends/ocr_backends_doc
5859
doc/observability/observability_doc
5960
doc/operations_layer/operations_layer_doc
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
Maker-Checker 審批閘門
2+
======================
3+
4+
有些自動化步驟後果太重大,不該由單一方說了算 —— 刪除正式環境資料、匯款、發布版
5+
本。``ApprovalGate`` 提供**職責分離(segregation of duties)**控制:由 *maker*
6+
提出請求並取得 token;*checker*(必須是**不同**的主體)核准或駁回;只有在 token
7+
被核准後動作才會繼續。
8+
9+
狀態存於選用的 JSON 檔,因此 maker(例如 CI 派發器)與 checker(例如人工審批者)
10+
可分屬不同程序執行。本模組為純標準函式庫,不匯入 ``PySide6``;token 使用
11+
:mod:`secrets`。
12+
13+
無頭 API
14+
--------
15+
16+
.. code-block:: python
17+
18+
from je_auto_control import ApprovalGate
19+
20+
gate = ApprovalGate("approvals.json") # 跨程序共用
21+
token = gate.request("delete prod table", requester="alice")
22+
23+
# 自我核准會被拒絕 —— checker 必須與 maker 不同。
24+
gate.approve(token, "alice") # -> False
25+
gate.approve(token, "bob") # -> True
26+
27+
if gate.is_approved(token):
28+
run_high_risk_action()
29+
30+
``reject(token, approver)`` 會封鎖動作;已決議的請求無法再次決議。
31+
``status(token)`` 回傳 ``pending`` / ``approved`` / ``rejected``(未知 token 為
32+
``None``),``get(token)`` 回傳完整紀錄,``pending()`` 則列出所有仍待決議的請求。
33+
34+
執行器指令
35+
----------
36+
37+
================================ ===================================================
38+
指令 效果
39+
================================ ===================================================
40+
``AC_approval_request`` 為 ``action`` 提出請求;回傳 ``{token}``。
41+
``AC_approval_approve`` 以 ``approver`` 核准 ``token``;``{approved}``。
42+
``AC_approval_reject`` 以 ``approver`` 駁回 ``token``;``{rejected}``。
43+
``AC_approval_status`` 回傳 ``{status, approved}`` 以閘控動作。
44+
================================ ===================================================
45+
46+
每個指令都接受選用的 ``db`` 路徑,讓流程可將請求保存到共用 JSON 檔。相同操作亦提供
47+
為 MCP 工具(``ac_approval_request`` / ``ac_approval_approve`` /
48+
``ac_approval_reject`` / ``ac_approval_status``),以及 Script Builder 中 **Tools**
49+
分類下的指令。

‎docs/source/Zh/zh_index.rst‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ AutoControl 所有功能的完整使用指南。
5454
doc/new_features/v29_features_doc
5555
doc/new_features/v30_features_doc
5656
doc/new_features/v31_features_doc
57+
doc/new_features/v32_features_doc
5758
doc/ocr_backends/ocr_backends_doc
5859
doc/observability/observability_doc
5960
doc/operations_layer/operations_layer_doc

‎je_auto_control/__init__.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,8 @@
203203
from je_auto_control.utils.plugin_sdk import (
204204
COMMANDS_GROUP, discover_plugins, load_plugins,
205205
)
206+
# Maker-checker approval gate (segregation of duties for high-risk actions)
207+
from je_auto_control.utils.governance import ApprovalGate
206208
# Background popup/interrupt watchdog (unattended automation)
207209
from je_auto_control.utils.watchdog import (
208210
PopupWatchdog, WatchdogRule, default_popup_watchdog,
@@ -638,6 +640,7 @@ def start_autocontrol_gui(*args, **kwargs):
638640
"describe_step", "generate_sop", "write_sop",
639641
"easing_names", "tween_drag", "tween_points",
640642
"COMMANDS_GROUP", "discover_plugins", "load_plugins",
643+
"ApprovalGate",
641644
# MCP server
642645
"AuditLogger", "HttpMCPServer", "MCPContent", "MCPPrompt",
643646
"MCPPromptArgument", "MCPResource", "MCPServer", "MCPTool",

‎je_auto_control/gui/script_builder/command_schema.py‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,41 @@ def _add_misc_specs(specs: List[CommandSpec]) -> None:
693693
default="je_auto_control.commands"),),
694694
description="Discover + register third-party plugin commands.",
695695
))
696+
specs.append(CommandSpec(
697+
"AC_approval_request", "Tools", "Approval: Request",
698+
fields=(
699+
FieldSpec("action", FieldType.STRING),
700+
FieldSpec("requester", FieldType.STRING, optional=True),
701+
FieldSpec("db", FieldType.STRING, optional=True),
702+
),
703+
description="Maker-checker: file a high-risk action for approval.",
704+
))
705+
specs.append(CommandSpec(
706+
"AC_approval_approve", "Tools", "Approval: Approve",
707+
fields=(
708+
FieldSpec("token", FieldType.STRING),
709+
FieldSpec("approver", FieldType.STRING),
710+
FieldSpec("db", FieldType.STRING, optional=True),
711+
),
712+
description="Approve a request (approver must differ from requester).",
713+
))
714+
specs.append(CommandSpec(
715+
"AC_approval_reject", "Tools", "Approval: Reject",
716+
fields=(
717+
FieldSpec("token", FieldType.STRING),
718+
FieldSpec("approver", FieldType.STRING),
719+
FieldSpec("db", FieldType.STRING, optional=True),
720+
),
721+
description="Reject a request (approver must differ from requester).",
722+
))
723+
specs.append(CommandSpec(
724+
"AC_approval_status", "Tools", "Approval: Status",
725+
fields=(
726+
FieldSpec("token", FieldType.STRING),
727+
FieldSpec("db", FieldType.STRING, optional=True),
728+
),
729+
description="Report a request's status and approved flag.",
730+
))
696731
specs.append(CommandSpec(
697732
"AC_generate_sop", "Report", "Generate SOP Document",
698733
fields=(

‎je_auto_control/utils/executor/action_executor.py‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2894,6 +2894,34 @@ def _load_plugins(group: str = "je_auto_control.commands") -> Dict[str, Any]:
28942894
return {"loaded": load_plugins(group)}
28952895

28962896

2897+
def _approval_request(action: str, requester: str = "",
2898+
db: Optional[str] = None) -> Dict[str, Any]:
2899+
"""Adapter: file a maker-checker approval request; return its token."""
2900+
from je_auto_control.utils.governance import ApprovalGate
2901+
return {"token": ApprovalGate(db).request(action, requester)}
2902+
2903+
2904+
def _approval_approve(token: str, approver: str,
2905+
db: Optional[str] = None) -> Dict[str, Any]:
2906+
"""Adapter: approve a request as ``approver`` (must differ from maker)."""
2907+
from je_auto_control.utils.governance import ApprovalGate
2908+
return {"approved": ApprovalGate(db).approve(token, approver)}
2909+
2910+
2911+
def _approval_reject(token: str, approver: str,
2912+
db: Optional[str] = None) -> Dict[str, Any]:
2913+
"""Adapter: reject a request as ``approver`` (must differ from maker)."""
2914+
from je_auto_control.utils.governance import ApprovalGate
2915+
return {"rejected": ApprovalGate(db).reject(token, approver)}
2916+
2917+
2918+
def _approval_status(token: str, db: Optional[str] = None) -> Dict[str, Any]:
2919+
"""Adapter: report the status and approved flag of a request token."""
2920+
from je_auto_control.utils.governance import ApprovalGate
2921+
gate = ApprovalGate(db)
2922+
return {"status": gate.status(token), "approved": gate.is_approved(token)}
2923+
2924+
28972925
class Executor:
28982926
"""
28992927
Executor
@@ -3125,6 +3153,10 @@ def __init__(self):
31253153
"AC_tween_drag": _tween_drag,
31263154
"AC_list_plugins": _list_plugins,
31273155
"AC_load_plugins": _load_plugins,
3156+
"AC_approval_request": _approval_request,
3157+
"AC_approval_approve": _approval_approve,
3158+
"AC_approval_reject": _approval_reject,
3159+
"AC_approval_status": _approval_status,
31283160
"AC_a11y_record_start": _a11y_record_start,
31293161
"AC_a11y_record_stop": _a11y_record_stop,
31303162
"AC_a11y_record_events": _a11y_record_events,

0 commit comments

Comments
 (0)