Skip to content

Commit 9f39baf

Browse files
johnteeecursoragent
andcommitted
feat: wire H4 policy and RBAC in shadow mode (Sprint 2)
Add governance h4_integration for approval-path policy shadow logging and subagent launch RBAC (shadow default, enforce via env). Defer consensus_validation per ADR 0029; update receipts and wiring watch-list. Constraint: policy shadow never blocks; RBAC enforce opt-in only Tested: test_h4_shadow_wiring.py; test_validate_wiring.py; run_test_tier.py --tier smoke (159 passed) Confidence: high Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 1c695dd commit 9f39baf

17 files changed

Lines changed: 407 additions & 19 deletions
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# ADR 0029: Consensus Validation Deferred Behind Approval Queue
2+
3+
## Status
4+
5+
Accepted — 2026-06-10
6+
7+
**Expiry review:** 2026-12-10 (re-score whether `consensus_validation` should gate destructive actions)
8+
9+
## Context
10+
11+
Horizon H4 delivered `teaagent/consensus_validation.py` (~600 lines) with passing
12+
tests but no production import path (ENG-R1). Sprint 2 wired policy engine and
13+
RBAC in shadow/enforce mode. Consensus validation overlaps with:
14+
15+
- Existing centralized subagent approval queue (ADR 0022)
16+
- Federated swarm consensus in `teaagent/consensus.py` (ADR 0019)
17+
18+
Shipping a third consensus surface without wiring invites doc⇄reality drift.
19+
20+
## Decision
21+
22+
**Defer** wiring `consensus_validation` into the destructive-action path until
23+
2026-12-10. Until then:
24+
25+
1. `consensus_validation` remains labeled `experimental — unwired`.
26+
2. Destructive actions continue to flow through the existing approval queue and
27+
JIT approval coordinator — no duplicate consensus gate.
28+
3. WDA-006 acceptance is met by this ADR plus the wiring validator watch-list.
29+
30+
## Consequences
31+
32+
- Positive: avoids parallel consensus systems; Sprint 2 scope stays bounded.
33+
- Negative: multi-agent consensus claims must not cite `consensus_validation` as live.
34+
- Follow-up: on expiry, choose **wire behind approval queue** or **delete/quarantine**
35+
with import-graph evidence.
36+
37+
## References
38+
39+
- [Work Direction Decomposition (WDA-006)](../plans/work-direction-decomposition-2026-06-10.md)
40+
- [Engineering Critique Refresh (ENG-R1)](../analysis/engineering-critique-refresh-2026-06-10.md)
41+
- ADR 0019, ADR 0022

docs/adr/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ This directory contains all Architecture Decision Records (ADRs) for the TeaAgen
3131
| 0023 | Strict Plan-Before-Write Enforcement | Accepted and Implemented | 2026-05-29 | - |
3232
| 0024 | Automated Memory Invalidation | Accepted and Implemented | 2026-05-29 | - |
3333
| 0025 | Shared ChatSessionController for Chat Surfaces | Accepted and Implemented | 2026-06-01 | 2026-06-04 13:18:00 +0800 |
34+
| 0029 | Consensus Validation Deferred Behind Approval Queue | Accepted | 2026-06-10 | 2026-12-10 (expiry review) |
3435

3536
## ADR Categories
3637

docs/generated/docs-inventory.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
Generated by `python3 scripts/generate_docs_inventory.py`.
77
Do not edit this file manually — regenerate instead.
88

9-
**Markdown files:** 551
9+
**Markdown files:** 552
1010

1111
| Path | Bytes | SHA256 (12) |
1212
| --- | ---: | --- |
@@ -39,6 +39,7 @@ Do not edit this file manually — regenerate instead.
3939
| `adr/0026-cli-execution-abstraction-layer.md` | 560 | `2498fb2f04a4` |
4040
| `adr/0027-context-bus-architecture.md` | 543 | `6fa1d2ced665` |
4141
| `adr/0028-tournament-swarm-architecture.md` | 594 | `ee8dec0fdb60` |
42+
| `adr/0029-consensus-validation-deferred.md` | 1587 | `8a2da40abc07` |
4243
| `adr/README.md` | 6552 | `83d807309b2d` |
4344
| `agent-mode-operator-guide.md` | 2778 | `25b258ab7bfe` |
4445
| `analysis/active-findings-status-ledger-2026-06-06.md` | 4724 | `34c514f544b8` |
@@ -448,7 +449,7 @@ Do not edit this file manually — regenerate instead.
448449
| `plans/ticket-plans/WDG-002-plan.md` | 1712 | `16cb2bb47cbc` |
449450
| `plans/ux-improvement-roadmap-2026-05-31.md` | 15201 | `368416e593d4` |
450451
| `plans/work-direction-decomposition-2026-06-10.md` | 10371 | `cba4dd33a15d` |
451-
| `plans/work-direction-execution-index-2026-06-10.md` | 4993 | `6400e46356aa` |
452+
| `plans/work-direction-execution-index-2026-06-10.md` | 5011 | `94a33014dd66` |
452453
| `plugin-skill-catalog.md` | 4118 | `8d42b8f0c492` |
453454
| `processes/breaking-changes.md` | 820 | `2a43f4d37b6c` |
454455
| `processes/community-presence.md` | 5009 | `f33f69b2e8ff` |
@@ -487,7 +488,7 @@ Do not edit this file manually — regenerate instead.
487488
| `reviews/project-state-critical-questioning-2026-06-04.md` | 7340 | `78b9b54c3a9c` |
488489
| `reviews/security-risk-assessment-2026-06-02.md` | 24112 | `4c9e2e00d001` |
489490
| `reviews/seven-control-loops-critical-questioning-2026-06-05.md` | 7531 | `ae1e34b8369d` |
490-
| `roadmap-status.md` | 19951 | `52f9b5a28fdf` |
491+
| `roadmap-status.md` | 19926 | `3c7c01ed0772` |
491492
| `run-evidence-and-audit-guide.md` | 1980 | `97b527c850b1` |
492493
| `security-whitepaper.md` | 9691 | `d65a19a755cb` |
493494
| `security/approval-abuse-cases-2026-06-02.md` | 1281 | `4c43296d1c66` |

docs/plans/work-direction-execution-index-2026-06-10.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ any time after S1 WDA-001 lands (needs honest module labels for concept audit).
6969

7070
| Sprint | IDs | Notes |
7171
| --- | --- | --- |
72-
| S2 | WDA-002, WDA-003, WDA-006 | Shadow policy + RBAC; consensus ADR |
72+
| S2 | WDA-002, WDA-003, WDA-006 | **Closed** — shadow policy + RBAC enforce; ADR 0029 |
7373
| S3 | WDA-004, WDA-005, WDD-001, WDD-002 | Release gate CI; single-platform update proof |
7474
| S4 | WDC-002, WDC-003, WDC-004 | Three-concept onboarding; terminology freeze |
7575
| S5 | WDE-001, WDE-002, WDE-003, WDF-001, WDF-002 | Remote backend; root-module freeze |

docs/roadmap-status.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Provide a single source of truth for roadmap item status, ownership, confidence,
2929
| H1 | Daily operator loop | Setup, daily cockpit, plan, execute, approve, verify, recover, and remember are one coherent journey | governance | Complete | High | H2 | Journey acceptance tests pass across CLI/TUI baseline; acceptance tier 628 passed at `85109e4` (2026-06-10) |
3030
| H2 | Multi-surface continuity | CLI, TUI, IDE, dashboard, background, cloud, and gateway share one run-state contract | TBD | Partially fixed — M2 foundation wired | Medium | WDA-002 | M2 acceptance complete; full surface parity (IDE/dashboard/cloud) still open |
3131
| H3 | Ecosystem trust | MCP, plugins, skills, hooks, subagents, and automations are explainable, revocable, and testable | TBD | Partially fixed — M3 tests pass | Medium | WDC-002 | M3 acceptance complete; general-user trust onboarding simplification still open |
32-
| H4 | Durable team operations | Long-running and team workflows have durable execution, control-plane views, policy, audit, and cost attribution | TBD | Partially fixed — unwired | Low | WDA-002 | RBAC/policy/consensus modules exist but unwired; cockpit data source only wired H4 surface (ENG-R1) |
32+
| H4 | Durable team operations | Long-running and team workflows have durable execution, control-plane views, policy, audit, and cost attribution | TBD | Partially fixed — shadow wired | Low | WDA-004 | Policy/RBAC shadow-wired (WDA-002/003); consensus deferred (ADR 0029) |
3333
| H5 | Quality and eval loop | Prompt/runtime/model changes cannot silently degrade daily outcomes | TBD | Partially fixed — unwired | Low | WDA-004 | `context_health` wired via TUI; eval suite/release gate clusters unwired (ENG-R1) |
3434
| H6 | Packaging and adoption | Desktop/client-server and external-facing release channels have supply-chain, update, and support plans | TBD | Partially fixed — unwired | Low | WDA-005 | `update/*` package implemented but unwired; no single-platform proof yet |
3535

scripts/run_test_tier.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
str(_TESTS / 'test_phase5_context_bus.py'),
3131
str(_TESTS / 'test_governance_hardening.py'),
3232
str(_TESTS / 'test_validate_wiring.py'),
33+
str(_TESTS / 'test_h4_shadow_wiring.py'),
3334
str(_TESTS / 'regression'),
3435
)
3536

scripts/validate_wiring.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@
2828

2929
# H4/H5/H6 clusters from ENG-R1 — must be production-wired or explicitly labeled.
3030
WATCH_MODULES: tuple[str, ...] = (
31-
'teaagent.rbac',
32-
'teaagent.policy_engine',
3331
'teaagent.policy_routing',
3432
'teaagent.consensus_validation',
3533
'teaagent.release_gate',

teaagent/governance/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@
44
AuditCompletenessReport,
55
check_audit_completeness,
66
)
7+
from teaagent.governance.h4_integration import (
8+
H4GovernanceMode,
9+
check_subagent_launch_rbac,
10+
evaluate_approval_policy_shadow,
11+
policy_governance_mode,
12+
rbac_governance_mode,
13+
)
714
from teaagent.governance.plan_gate import (
815
WRITE_TOOLS,
916
ReviewGate,
@@ -18,7 +25,12 @@
1825
'ToolLintIssue',
1926
'WRITE_TOOLS',
2027
'assert_write_allowed',
28+
'H4GovernanceMode',
2129
'check_audit_completeness',
30+
'check_subagent_launch_rbac',
31+
'evaluate_approval_policy_shadow',
2232
'lint_registry',
33+
'policy_governance_mode',
34+
'rbac_governance_mode',
2335
'require_review_gate',
2436
]
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
"""H4 governance shadow wiring (WDA-002 / WDA-003).
2+
3+
Connects the policy engine and RBAC modules to production entry paths in
4+
shadow mode by default. RBAC may be switched to enforce via
5+
``TEAAGENT_H4_RBAC_MODE=enforce``.
6+
"""
7+
8+
from __future__ import annotations
9+
10+
import os
11+
from enum import Enum
12+
from pathlib import Path
13+
from typing import Any, Optional
14+
15+
from teaagent.policy_engine import PolicyEffect, PolicyEngine, PolicyStore, PolicyType
16+
17+
18+
class H4GovernanceMode(str, Enum):
19+
SHADOW = 'shadow'
20+
ENFORCE = 'enforce'
21+
22+
23+
def _mode_from_env(var_name: str, *, default: H4GovernanceMode) -> H4GovernanceMode:
24+
raw = os.environ.get(var_name, default.value).strip().lower()
25+
if raw in {m.value for m in H4GovernanceMode}:
26+
return H4GovernanceMode(raw)
27+
return default
28+
29+
30+
def policy_governance_mode() -> H4GovernanceMode:
31+
return _mode_from_env('TEAAGENT_H4_POLICY_MODE', default=H4GovernanceMode.SHADOW)
32+
33+
34+
def rbac_governance_mode() -> H4GovernanceMode:
35+
return _mode_from_env('TEAAGENT_H4_RBAC_MODE', default=H4GovernanceMode.SHADOW)
36+
37+
38+
def _policy_engine_for_root(root: str | Path) -> PolicyEngine:
39+
return PolicyEngine(PolicyStore(Path(root).resolve()))
40+
41+
42+
def record_h4_shadow_event(
43+
audit: Any,
44+
run_id: str,
45+
*,
46+
surface: str,
47+
mode: H4GovernanceMode,
48+
allowed: bool,
49+
reason: str,
50+
context: dict[str, Any],
51+
enforced: bool,
52+
details: Optional[list[dict[str, Any]]] = None,
53+
) -> None:
54+
audit.record(
55+
'h4_governance_shadow',
56+
run_id,
57+
surface=surface,
58+
mode=mode.value,
59+
allowed=allowed,
60+
enforced=enforced,
61+
reason=reason,
62+
context=context,
63+
details=details or [],
64+
)
65+
66+
67+
def evaluate_approval_policy_shadow(
68+
*,
69+
workspace_root: str | Path | None,
70+
audit: Any,
71+
run_id: str,
72+
tool_name: str,
73+
arguments: dict[str, Any],
74+
destructive: bool,
75+
call_id: str,
76+
) -> bool:
77+
"""Evaluate approval policies and record a shadow receipt. Never blocks."""
78+
if workspace_root is None:
79+
return True
80+
81+
mode = policy_governance_mode()
82+
context = {
83+
'action': 'approve_tool',
84+
'tool_name': tool_name,
85+
'call_id': call_id,
86+
'destructive': destructive,
87+
'arguments': arguments,
88+
}
89+
engine = _policy_engine_for_root(workspace_root)
90+
effect, details = engine.evaluate_with_explanation(
91+
context,
92+
policy_type=PolicyType.APPROVAL,
93+
)
94+
allowed = effect == PolicyEffect.ALLOW
95+
denying = next(
96+
(d for d in details if d.get('applies') and d.get('effect') == 'deny'),
97+
None,
98+
)
99+
reason = (
100+
f'Policy {denying["policy_id"]} would deny'
101+
if denying
102+
else 'Policy evaluation would allow'
103+
)
104+
record_h4_shadow_event(
105+
audit,
106+
run_id,
107+
surface='approval',
108+
mode=mode,
109+
allowed=allowed,
110+
reason=reason,
111+
context=context,
112+
enforced=False,
113+
details=details,
114+
)
115+
return True
116+
117+
118+
def check_subagent_launch_rbac(
119+
*,
120+
workspace_root: str | Path,
121+
audit: Any | None,
122+
parent_run_id: str,
123+
assignee: str,
124+
def_name: str,
125+
depth: int,
126+
) -> tuple[bool, str]:
127+
"""RBAC gate for subagent launch. Shadow by default; enforce when configured."""
128+
from teaagent.rbac import Permission, RBACSystem
129+
130+
mode = rbac_governance_mode()
131+
context = {
132+
'action': 'launch_subagent',
133+
'subagent': def_name,
134+
'depth': depth,
135+
'parent_run_id': parent_run_id,
136+
}
137+
rbac = RBACSystem(workspace_root)
138+
allowed, reason = rbac.check_action_permission(
139+
assignee,
140+
'start_workflow',
141+
context,
142+
)
143+
if audit is not None:
144+
record_h4_shadow_event(
145+
audit,
146+
parent_run_id,
147+
surface='subagent_launch',
148+
mode=mode,
149+
allowed=allowed,
150+
reason=reason,
151+
context={
152+
**context,
153+
'assignee': assignee,
154+
'permission': Permission.START_WORKFLOW.value,
155+
},
156+
enforced=mode == H4GovernanceMode.ENFORCE and not allowed,
157+
details=[],
158+
)
159+
if mode == H4GovernanceMode.ENFORCE and not allowed:
160+
return False, reason
161+
return True, reason

teaagent/policy_engine.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Policy engine for collaboration rules and team operations.
22
3-
experimental — unwired
3+
Wired in shadow mode via ``teaagent.governance.h4_integration`` (WDA-002).
44
55
This module provides the foundation for defining, storing, and evaluating
66
policies for collaborative agent workflows, including role-based access

0 commit comments

Comments
 (0)