refactor(ack): default OMP workers to yolo approval, drop approvalMode switch

This commit is contained in:
2026-08-23 22:32:30 +08:00
parent e0fffd6845
commit d33bc3ccaf
8 changed files with 22 additions and 90 deletions
+2 -2
View File
@@ -10,7 +10,7 @@
2. 增加 OMP 的模型 allowlist 与 role/profile 配置能力;模型使用 OMP 要求的精确 `provider/model` 选择器,例如 `opencode-go/gpt-5.6-luna`,不从当前 Coordinator 会话自动推断。
3. launcher 在固定可信目录中解析 `omp`,探测并记录版本,生成确定性的启动 argv,并把 CLI、模型、thinking、审批模式、worktree 和版本纳入 fingerprint/receipt。
4. OMP worker 使用交互式 `omp` 命令,通过结构化参数指定模型、thinking、approval mode 和工作目录;Orca 仍负责 terminal orchestration 与任务 dispatch。
5. OMP profile 不允许写入自由 command、额外 argv、shell、环境变量或凭据值;不使用 `--auto-approve``--plan-yolo` 或会话复用;是否启用 `--approval-mode yolo` 由 tasks.yaml 的 `approvalMode` 配置决定(可开可关,缺省 workspace-write 开启)
5. OMP profile 不允许写入自由 command、额外 argv、shell、环境变量或凭据值;不使用 `--auto-approve``--plan-yolo` 或会话复用;`--approval-mode yolo` 是 OMP workspace-write worker 的默认审批模式,规则层直接允许
6. 为 OMP 增加 profile 校验、argv 渲染、环境凭据隔离、可信 executable 解析和 launcher plan 的白盒/黑盒测试。
7. 保持 Codex、Cursor、Grok 既有行为不变;不修改 ACK 的三角色职责或 delivery 流程。
@@ -26,7 +26,7 @@
- `omp` 可执行文件必须通过 ACK 固定可信目录解析,不能从调用者 PATH 任意拾取。
- OMP 模型 ID 必须由项目 allowlist 明确声明;当前会话中的 `opencode-go/gpt-5.6-luna` 只有在 profile 明确配置后才可使用。
- `approvalMode`(可选,仅 OMP profile)显式控制审批:`yolo` / `write` / `always-ask`;缺省 `workspace-write` → yolo、`read-only` → always-ask。
- 审批模式由 launcher 固定构造:`workspace-write` `--approval-mode yolo``read-only` `--approval-mode always-ask`
- `workspace-write` 下的 exec 工具是否会因 OMP 审批提示阻塞,由独立 Test 在黑盒环境中验证;若阻塞,必须记录为环境/运行模式问题,不伪报成功。
## 可观测验收标准
-3
View File
@@ -57,7 +57,6 @@ project:
model: "opencode-go/gpt-5.6-luna"
reasoningEffort: "medium"
permissionMode: "workspace-write"
approvalMode: "yolo"
omp-test-standard:
role: "test"
cli: "omp"
@@ -65,7 +64,6 @@ project:
model: "opencode-go/gpt-5.6-luna"
reasoningEffort: "low"
permissionMode: "workspace-write"
approvalMode: "yolo"
omp-dev-strong:
role: "developer"
cli: "omp"
@@ -73,7 +71,6 @@ project:
model: "opencode-go/gpt-5.6-luna"
reasoningEffort: "high"
permissionMode: "workspace-write"
approvalMode: "yolo"
defaults:
developer: "codex-dev-standard"
test: "codex-test-standard"
+1 -2
View File
@@ -208,8 +208,7 @@ v0.10 自动 launcher 仅支持 `read-only` 与 `workspace-write`。full-access
Codex bypass、Cursor YOLO/force、Grok `--yolo` / bypassPermissions 和关闭
sandbox 都会 fail closed;在有可信平台审批或独立签发通道之前,不用项目文件
伪装成用户授权。Grok worker 由 launcher 固定带 `--always-approve`,避免工具调用
停在确认框,sandbox 仍必须启用。v0.17 起 `cli: grok` 是一等 worker CLI`cli: omp` 使用 OMP 的结构化模型、thinking 和 approval-mode 参数
`approvalMode` 在 tasks.yaml profile 中显式开关 yolo(缺省 workspace-write 开启)。旧任务板没有结构化
停在确认框,sandbox 仍必须启用。v0.17 起 `cli: grok` 是一等 worker CLI`cli: omp` 使用 OMP 的结构化模型、thinking 和 approval-mode 参数workspace-write 默认 yolo。旧任务板没有结构化
`project.orchestration` 时仍可读取和手动协作,但不得自动创建 worker。
持久化 `receiptHash` 是无密钥 checksum,不是 launcher 身份证明。ACK 只复用同一轮
+2 -4
View File
@@ -251,10 +251,8 @@ description: >-
仍必须带 sandbox。
- OMP worker 使用结构化 `--model`、`--thinking` 和 `--approval-mode` 参数。
`--approval-mode yolo` 是 OMP worker 的审批模式,不是 CLI 绕过标志:规则层
直接允许,是否启用由 `tasks.yaml` profile 的 `approvalMode` 配置决定
`yolo` / `write` / `always-ask`,仅 `cli: omp` 可用;缺省
workspace-write → yolo、read-only → always-ask);仍禁止 `--auto-approve`
也不适用于 codex/cursor-agent/grok。
直接允许并默认启用(workspace-write → yolo、read-only → always-ask);
仍禁止 `--auto-approve`,也不适用于 codex/cursor-agent/grok。
- 不把无密钥 `receiptHash` 或 Orca live metadata 当作旧终端的启动 attestation
没有可信空闲状态、配置匹配和历史消息清理证明时不复用既有 worker。
- launcher 返回 `indeterminate` 或 `reconcile required` 时,不直接重试;先按
+4 -6
View File
@@ -88,10 +88,9 @@ worker 路由的机器可读事实只保存在 `docs/ack/tasks.yaml` 的
CLI 默认值。具体 argv 只能由 ACK 的可信 launcher 按 allowlist 构造。
OMP profile 使用 `cli: omp``model` 必须是精确的 `provider/model` 选择器,
`reasoningEffort` 映射为 `--thinking`可选的 `approvalMode` 字段在
`tasks.yaml` 中显式控制审批:`yolo` / `write` / `always-ask`,仅对 OMP
profile 有效。缺省时 `workspace-write` 映射为 `--approval-mode yolo`
`read-only` 映射为 `--approval-mode always-ask`。规则层直接允许 yolo
`reasoningEffort` 映射为 `--thinking`审批模式由 launcher 固定构造:
`workspace-write` 映射为 `--approval-mode yolo``read-only` 映射为
`--approval-mode always-ask`。规则层直接允许 yolo 并默认启用;
launcher 仍不接受 `--auto-approve` 或会话复用。
### 安全默认
@@ -105,8 +104,7 @@ launcher 仍不接受 `--auto-approve` 或会话复用。
Grok `--always-approve` 由 launcher 固定写入 argv,用来避免工具调用停在确认框;
它不关闭 `--sandbox`
OMP 的 `--approval-mode yolo` 不是上述 CLI 绕过标志。它是 OMP worker 的
审批模式,规则层直接允许,是否启用由 `tasks.yaml` profile 的
`approvalMode` 字段决定(可开可关,缺省 workspace-write 开启);它不授予
审批模式,规则层直接允许并默认启用(workspace-write);它不授予
full-access,不用于其它 CLI,也不等同于 Grok `--yolo`
- 项目可以更新模型 allowlist 或增加安全 profile,但不能通过文档绕开 schema 和
launcher。
+4 -24
View File
@@ -28,7 +28,6 @@ CLIS = frozenset({"codex", "cursor-agent", "grok", "omp"})
TIERS = frozenset({"standard", "strong"})
REASONING_EFFORTS = frozenset({"low", "medium", "high", "xhigh"})
PERMISSION_MODES = frozenset({"read-only", "workspace-write"})
OMP_APPROVAL_MODES = frozenset({"yolo", "write", "always-ask"})
ORCHESTRATION_MODES = frozenset({"orca", "manual"})
DEFAULT_KEYS = frozenset({"developer", "test", "developerUpgraded"})
CLI_REQUIRES_REASONING_EFFORT = frozenset({"codex", "grok", "omp"})
@@ -67,9 +66,6 @@ PROFILE_FIELDS = frozenset(
"permissionMode",
}
)
# approvalMode is optional and valid only for cli=omp: it switches whether the
# OMP worker runs with yolo approval. Rules allow it; tasks.yaml decides.
PROFILE_OPTIONAL_FIELDS = frozenset({"approvalMode"})
RECEIPT_FIELDS = frozenset(
{
"receiptVersion",
@@ -223,9 +219,7 @@ def validate_profile(profile: Any, *, where: str = "profile") -> list[str]:
if not isinstance(profile, dict):
return [f"{where}: must be an object"]
errors = _unknown_fields(
profile, PROFILE_FIELDS | PROFILE_OPTIONAL_FIELDS, where
)
errors = _unknown_fields(profile, PROFILE_FIELDS, where)
errors.extend(_missing_fields(profile, PROFILE_FIELDS, where))
role = profile.get("role")
@@ -234,18 +228,6 @@ def validate_profile(profile: Any, *, where: str = "profile") -> list[str]:
model = profile.get("model")
effort = profile.get("reasoningEffort")
permission = profile.get("permissionMode")
approval = profile.get("approvalMode")
if "approvalMode" in profile and cli != "omp":
errors.append(f"{where}.approvalMode: only valid for cli=omp")
if approval is not None:
if not isinstance(approval, str) or approval not in OMP_APPROVAL_MODES:
errors.append(
f"{where}.approvalMode: must be yolo/write/always-ask"
)
elif permission == "read-only" and approval != "always-ask":
errors.append(
f"{where}.approvalMode: read-only requires always-ask"
)
if not isinstance(role, str) or role not in ROLES:
errors.append(f"{where}.role: must be developer/test")
@@ -521,11 +503,9 @@ def render_worker_argv(
return argv
if cli == "omp":
# approvalMode is the explicit per-profile switch (yolo/write/always-ask).
# Default: workspace-write -> yolo, read-only -> always-ask.
omp_approval = profile.get("approvalMode")
if omp_approval is None:
omp_approval = "always-ask" if permission == "read-only" else "yolo"
# OMP workspace-write workers default to yolo approval (rules allow it);
# read-only workers always use always-ask.
omp_approval = "always-ask" if permission == "read-only" else "yolo"
return [
executable,
"--model",
-5
View File
@@ -377,11 +377,6 @@
"permissionMode": {
"type": "string",
"enum": ["read-only", "workspace-write"]
},
"approvalMode": {
"type": "string",
"enum": ["yolo", "write", "always-ask"],
"description": "OMP-only switch; default workspace-write -> yolo, read-only -> always-ask"
}
}
},
+9 -44
View File
@@ -15,8 +15,8 @@ import launch_worker # noqa: E402
import worker_profiles # noqa: E402
def omp_profile(*, role: str = "developer", permission: str = "workspace-write", approval: str | None = None) -> dict:
profile = {
def omp_profile(*, role: str = "developer", permission: str = "workspace-write") -> dict:
return {
"role": role,
"cli": "omp",
"tier": "standard",
@@ -24,12 +24,9 @@ def omp_profile(*, role: str = "developer", permission: str = "workspace-write",
"reasoningEffort": "low",
"permissionMode": permission,
}
if approval is not None:
profile["approvalMode"] = approval
return profile
def omp_orchestration(**profile_kwargs: object) -> dict:
def omp_orchestration() -> dict:
return {
"profileVersion": 1,
"mode": "orca",
@@ -41,7 +38,7 @@ def omp_orchestration(**profile_kwargs: object) -> dict:
}
},
"profiles": {
"omp-dev-standard": omp_profile(**profile_kwargs),
"omp-dev-standard": omp_profile(),
"omp-test-standard": omp_profile(role="test"),
},
"defaults": {
@@ -73,8 +70,8 @@ class OmpProfileValidationTests(unittest.TestCase):
self.assertTrue(any("Test cannot define a strong allowlist" in error for error in errors))
class OmpApprovalModeTests(unittest.TestCase):
def test_default_workspace_write_uses_yolo(self) -> None:
class OmpArgvTests(unittest.TestCase):
def test_workspace_write_defaults_to_yolo_approval(self) -> None:
argv = worker_profiles.render_worker_argv(
omp_profile(), "/usr/local/bin/omp", "/repo/demo"
)
@@ -96,45 +93,13 @@ class OmpApprovalModeTests(unittest.TestCase):
self.assertNotIn("--auto-approve", argv)
self.assertNotIn("--plan-yolo", argv)
def test_explicit_write_turns_approval_off(self) -> None:
argv = worker_profiles.render_worker_argv(
omp_profile(approval="write"), "/usr/local/bin/omp", "/repo/demo"
)
self.assertEqual(argv[argv.index("--approval-mode") + 1], "write")
def test_explicit_always_ask_on_workspace_write(self) -> None:
argv = worker_profiles.render_worker_argv(
omp_profile(approval="always-ask"), "/usr/local/bin/omp", "/repo/demo"
)
self.assertEqual(argv[argv.index("--approval-mode") + 1], "always-ask")
def test_read_only_defaults_to_always_ask(self) -> None:
def test_read_only_uses_always_ask_approval(self) -> None:
argv = worker_profiles.render_worker_argv(
omp_profile(permission="read-only"), "/usr/local/bin/omp", "/repo/demo"
)
self.assertIn("--approval-mode", argv)
self.assertEqual(argv[argv.index("--approval-mode") + 1], "always-ask")
def test_read_only_rejects_explicit_yolo(self) -> None:
routing = omp_orchestration(permission="read-only", approval="yolo")
errors = worker_profiles.validate_orchestration(routing)
self.assertTrue(any("read-only requires always-ask" in error for error in errors))
def test_approval_mode_is_only_valid_for_omp(self) -> None:
routing = omp_orchestration()
codex = routing["profiles"]["omp-dev-standard"].copy()
codex["cli"] = "codex"
codex["approvalMode"] = "yolo"
routing["profiles"]["codex-dev-standard"] = codex
routing["modelAllowlist"]["codex"] = {
"developer": {"standard": ["opencode-go/gpt-5.6-luna"]}
}
errors = worker_profiles.validate_orchestration(routing)
self.assertTrue(any("only valid for cli=omp" in error for error in errors))
def test_invalid_approval_value_is_rejected(self) -> None:
routing = omp_orchestration(approval="always-prompt")
errors = worker_profiles.validate_orchestration(routing)
self.assertTrue(any("must be yolo/write/always-ask" in error for error in errors))
self.assertEqual(argv[-2:], ["/repo/demo", "--no-session"])
def test_omp_executable_identity_matches_only_omp(self) -> None:
self.assertTrue(worker_profiles.executable_basename_matches_cli("/bin/omp", "omp"))