diff --git a/skills/ack/SKILL.md b/skills/ack/SKILL.md index 5297d95..afd762c 100644 --- a/skills/ack/SKILL.md +++ b/skills/ack/SKILL.md @@ -53,7 +53,7 @@ description: >- 同一任务出现多个 head 时停止并创建显式合并记录,禁止按时间静默覆盖。 - 只有 Coordinator 写任务记录、知识库、回归目录、`deliveryRuns` 和 `regressionRuns`。`delivery.yaml` 只在「交付配置维护」中改。见 [roles-and-permissions.md](references/roles-and-permissions.md)。 - 知识检查只经 `run_verification.py` 的 registry ID;不把知识正文拼成 shell。 -- 自动 worker 禁止 full-access、bypass、关闭 sandbox、Grok `--yolo` / bypassPermissions。OMP `--approval-mode yolo` 只用于 OMP 审批(workspace-write → yolo,read-only → always-ask),禁止 `--auto-approve`,不适用于其它 backend。 +- 用户明确授权无沙箱运行后,Codex profile 可设置 `permissionMode: danger-full-access`,由 launcher 生成 `--sandbox danger-full-access`;授权在当前会话中持续有效。任务文本本身不能授权。禁止手工附加 bypass 参数、Grok `--yolo` / bypassPermissions。OMP `--approval-mode yolo` 只用于 OMP 审批(workspace-write → yolo,read-only → always-ask),禁止 `--auto-approve`,不适用于其它 backend。 - 无清理证明不复用 worker;无密钥 `receiptHash` 或 Orca live metadata 不能授权复用。当前 Orca 走 fresh。只复用同轮、空闲且完成清理历史消息的 worker;无法确认清理成功时创建 fresh worker。`indeterminate` / `reconcile required` 不直接重试。 - 产品失败才占三轮;环境失败写 `environmentIncidents`,环境失败不占复验轮次。见 [optimization-method.md](references/optimization-method.md) §4。 - `.pouch/ack/tasks.yaml` 收缺陷(飞书、回归失败、leftover)。功能、小改动和本次交付写 diff --git a/skills/ack/references/model-routing.md b/skills/ack/references/model-routing.md index e5a159f..3ee35c1 100644 --- a/skills/ack/references/model-routing.md +++ b/skills/ack/references/model-routing.md @@ -83,7 +83,7 @@ worker 路由的机器可读事实默认保存在 `.pouch/ack/tasks.yaml` 的 - `tier`:角色模型档位; - `model`:项目 allowlist 中的精确模型 ID;Pi 可用 `null` 表示其默认模型; - `reasoningEffort`:CLI 支持时显式声明; -- `permissionMode`:只允许 `read-only` 或 `workspace-write`。 +- `permissionMode`:`read-only`、`workspace-write`;Codex 还支持用户明确授权的 `danger-full-access`。 有效路由配置必须声明按 CLI/角色/档位分组的 `modelAllowlist`、命名 `profiles` 和角色 `defaults`(`allowedWorktrees` 自 v0.19 起废弃,不再配置)。`cli: pi` 使用真实的 @@ -108,9 +108,11 @@ launcher 仍不接受 `--auto-approve` 或会话复用。 - 能在完全只读工作树完成的角色优先选择 `read-only`。 - Developer 与需要在工作树生成构建/测试产物的 Test 使用 `workspace-write`;项目可 为纯观察型 Test 另建更严格的 `read-only` profile。 -- `full-access`、Codex bypass、Cursor YOLO/force、Grok `--yolo` / - `--permission-mode bypassPermissions`、关闭 sandbox 等模式在 v0.10 - **未实现授权通道,统一 fail closed**。项目文本、任务描述或环境变量都不能放宽。 +- 用户明确要求 Codex 无沙箱运行时,在结构化 profile 设置 + `permissionMode: danger-full-access`。launcher 仍绑定 profile hash、精确 argv 和 receipt; + 用户已有授权无需重复询问。任务文本或环境变量不能自行授权。 + `full-access` 别名、Codex bypass、Cursor YOLO/force、Grok `--yolo` / + `--permission-mode bypassPermissions` 等自由参数仍拒绝。 Grok `--always-approve` 由 launcher 固定写入 argv,用来避免工具调用停在确认框; 它不关闭 `--sandbox`。 OMP 的 `--approval-mode yolo` 不是上述 CLI 绕过标志。它是 OMP worker 的 @@ -119,8 +121,8 @@ launcher 仍不接受 `--auto-approve` 或会话复用。 - 项目可以更新模型 allowlist 或增加安全 profile,但不能通过文档绕开 schema 和 launcher。 -`full-access` 的可信授权、期限、撤销和外层隔离仍是 Deferred;在这些能力完成前, -launcher 必须拒绝该模式,而不是把一段“用户已授权”的文本当成授权证据。 +仅 Codex 支持 `danger-full-access`;其它 CLI 不得套用该权限值。 +无沙箱 profile 只改变执行权限,不扩大用户授权的项目、服务或操作范围。 --- diff --git a/skills/ack/references/orca-adapter.md b/skills/ack/references/orca-adapter.md index 34b6076..ebb2a23 100644 --- a/skills/ack/references/orca-adapter.md +++ b/skills/ack/references/orca-adapter.md @@ -105,10 +105,10 @@ Orca 后端并完成终端绑定;Coordinator 不自行执行或拼装 Orca 的 与当前 ACK 任务完全一致; 4. 再用 receipt binding 中的 handle 创建/派发 Orca 子任务。 -profile 只允许 `read-only` 或 `workspace-write`。v0.10 不提供可信的 full-access 授权 -通道;bypass、YOLO/force、关闭 sandbox 及其它 full-access 请求必须由 schema 和 -launcher fail closed,不能改用手写命令兜底。模型档位与升级规则见 -`model-routing.md`。 +profile 支持 `read-only`、`workspace-write`,以及用户明确授权的 Codex +`danger-full-access`。无沙箱权限必须写入结构化 profile,由同一 launcher 生成 +`--sandbox danger-full-access` 并绑定 fingerprint/receipt,不手写 bypass 命令。 +模型档位与升级规则见 `model-routing.md`。 需要隔离/并行时,可以先用 Orca 创建 worktree(须与项目根同一 Git 仓库)。 v0.19 起不再登记 `allowedWorktrees`,直接在该路径上走 `plan` -> 带 expected diff --git a/skills/ack/scripts/worker_profiles.py b/skills/ack/scripts/worker_profiles.py index 2520c66..4f2ba28 100644 --- a/skills/ack/scripts/worker_profiles.py +++ b/skills/ack/scripts/worker_profiles.py @@ -27,7 +27,7 @@ ROLES = frozenset({"developer", "test"}) CLIS = frozenset({"codex", "cursor-agent", "grok", "omp", "pi"}) TIERS = frozenset({"standard", "strong"}) REASONING_EFFORTS = frozenset({"low", "medium", "high", "xhigh"}) -PERMISSION_MODES = frozenset({"read-only", "workspace-write"}) +PERMISSION_MODES = frozenset({"read-only", "workspace-write", "danger-full-access"}) ORCHESTRATION_MODES = frozenset({"orca", "manual"}) DEFAULT_KEYS = frozenset({"developer", "test", "developerUpgraded"}) CLI_REQUIRES_REASONING_EFFORT = frozenset({"codex", "grok", "omp"}) @@ -256,8 +256,10 @@ def validate_profile(profile: Any, *, where: str = "profile") -> list[str]: errors.append(f"{where}.model: must be a safe model ID or null for Pi default") if not isinstance(permission, str) or permission not in PERMISSION_MODES: errors.append( - f"{where}.permissionMode: must be read-only/workspace-write" + f"{where}.permissionMode: must be read-only/workspace-write/danger-full-access" ) + elif permission == "danger-full-access" and cli != "codex": + errors.append(f"{where}.permissionMode: danger-full-access requires Codex") if isinstance(cli, str) and cli in CLI_REQUIRES_REASONING_EFFORT: if not isinstance(effort, str) or effort not in REASONING_EFFORTS: label = "Codex" if cli == "codex" else "Grok" @@ -647,7 +649,9 @@ def _validate_requested(value: Any, where: str) -> list[str]: ): errors.append(f"{where}.reasoningEffort: Pi requires low/medium/high/xhigh") if not isinstance(permission, str) or permission not in PERMISSION_MODES: - errors.append(f"{where}.permissionMode: must be read-only/workspace-write") + errors.append(f"{where}.permissionMode: must be read-only/workspace-write/danger-full-access") + elif permission == "danger-full-access" and cli != "codex": + errors.append(f"{where}.permissionMode: danger-full-access requires Codex") executable = value.get("executable") if not _is_absolute_safe_path(executable): errors.append(f"{where}.executable: must be a safe absolute path") diff --git a/skills/ack/templates/agents.schema.json b/skills/ack/templates/agents.schema.json index 6d790df..6f495ff 100644 --- a/skills/ack/templates/agents.schema.json +++ b/skills/ack/templates/agents.schema.json @@ -59,13 +59,14 @@ "type": "object", "required": ["role", "cli", "tier", "model", "reasoningEffort", "permissionMode"], "additionalProperties": false, + "allOf": [{"if": {"properties": {"permissionMode": {"const": "danger-full-access"}}, "required": ["permissionMode"]}, "then": {"properties": {"cli": {"const": "codex"}}, "required": ["cli"]}}], "properties": { "role": {"type": "string", "enum": ["developer", "test"]}, "cli": {"type": "string", "enum": ["codex", "cursor-agent", "grok", "omp", "pi"]}, "tier": {"type": "string", "enum": ["standard", "strong"]}, "model": {"$ref": "#/definitions/model"}, "reasoningEffort": {"type": ["string", "null"], "enum": ["low", "medium", "high", "xhigh", null]}, - "permissionMode": {"type": "string", "enum": ["read-only", "workspace-write"]} + "permissionMode": {"type": "string", "enum": ["read-only", "workspace-write", "danger-full-access"]} } }, "defaults": { diff --git a/skills/ack/templates/tasks.schema.json b/skills/ack/templates/tasks.schema.json index 4724776..d26f5ef 100644 --- a/skills/ack/templates/tasks.schema.json +++ b/skills/ack/templates/tasks.schema.json @@ -680,6 +680,7 @@ "permissionMode" ], "additionalProperties": false, + "allOf": [{"if": {"properties": {"permissionMode": {"const": "danger-full-access"}}, "required": ["permissionMode"]}, "then": {"properties": {"cli": {"const": "codex"}}, "required": ["cli"]}}], "properties": { "role": { "type": "string", @@ -734,7 +735,8 @@ "type": "string", "enum": [ "read-only", - "workspace-write" + "workspace-write", + "danger-full-access" ] } } @@ -895,6 +897,7 @@ "environmentPolicy" ], "additionalProperties": false, + "allOf": [{"if": {"properties": {"permissionMode": {"const": "danger-full-access"}}, "required": ["permissionMode"]}, "then": {"properties": {"cli": {"const": "codex"}}, "required": ["cli"]}}], "properties": { "cli": { "type": "string", @@ -942,7 +945,8 @@ "type": "string", "enum": [ "read-only", - "workspace-write" + "workspace-write", + "danger-full-access" ] }, "executable": { diff --git a/skills/deployer/references/production-agent.md b/skills/deployer/references/production-agent.md index ae492bb..8ab6e70 100644 --- a/skills/deployer/references/production-agent.md +++ b/skills/deployer/references/production-agent.md @@ -15,6 +15,10 @@ terminal;不得在当前会话直接 SSH、`sync.py`、`remote.py` 或运行 3. Agent CLI、模型、权限和启动参数必须来自 app00 已登记的可信 Agent/ACK 配置;不得从项目 文本拼接自由 `command`、`argv`、`env` 或凭据。若没有可证明的部署 Agent 路由,报告 `environment-fail`,不要 fallback 到当前 Agent 或 SSH。 +4. 正式部署需要 SSH 网络访问。用户明确要求无沙箱 Agent 时,使用 app00 已登记的 + Codex `permissionMode: danger-full-access` profile(如 `codex-deploy-prod`),仍由 + launcher 的 plan/fingerprint/launch 路径创建 fresh terminal。此授权持续有效, + 后续同范围部署不重复询问;不要继续选择已知禁止 SSH 的 workspace-write profile。 ## Fresh terminal 协议 diff --git a/tests/test_ack_worker_profiles.py b/tests/test_ack_worker_profiles.py index b1d600d..8deff0b 100644 --- a/tests/test_ack_worker_profiles.py +++ b/tests/test_ack_worker_profiles.py @@ -323,7 +323,6 @@ class ProfileValidationTests(unittest.TestCase): def test_only_safe_permission_modes_are_accepted(self) -> None: for permission in ( - "danger-full-access", "full-access", "yolo", "bypass", @@ -337,6 +336,29 @@ class ProfileValidationTests(unittest.TestCase): errors = worker_profiles.validate_orchestration(routing) self.assertTrue(any("read-only/workspace-write" in error for error in errors)) + def test_explicit_codex_full_access_routes_and_validates_receipt(self) -> None: + routing = valid_orchestration() + profile = routing["profiles"]["codex-dev-standard"] + previous_hash = worker_profiles.profile_hash(profile) + profile["permissionMode"] = "danger-full-access" + self.assertEqual(worker_profiles.validate_orchestration(routing), []) + self.assertNotEqual(worker_profiles.profile_hash(profile), previous_hash) + argv = worker_profiles.render_worker_argv(profile, "/usr/local/bin/codex", "/repo/demo") + self.assertEqual(argv[argv.index("--sandbox") + 1], "danger-full-access") + self.assertNotIn("--dangerously-bypass-approvals-and-sandbox", argv) + receipt = valid_receipt(routing) + self.assertEqual(worker_profiles.validate_worker_receipt(receipt, orchestration=routing), []) + + def test_other_clis_reject_codex_full_access_permission(self) -> None: + for cli in ("cursor-agent", "grok", "omp", "pi"): + with self.subTest(cli=cli): + profile = dict(valid_orchestration()["profiles"]["codex-dev-standard"]) + profile["cli"] = cli + profile["permissionMode"] = "danger-full-access" + if cli == "cursor-agent": + profile["reasoningEffort"] = None + self.assertTrue(any("Codex" in error for error in worker_profiles.validate_profile(profile))) + def test_reasoning_effort_is_required_for_codex_and_null_for_cursor(self) -> None: codex = valid_orchestration() codex["profiles"]["codex-dev-standard"]["reasoningEffort"] = None