| name | authority-enforcer |
| description | Validates and enforces agent authority boundaries during wave execution and review panels |
| triggers | ["authority","boundary","domain","enforce","validate","filter"] |
| token_cost | low |
| summary | Loads authority matrix, validates agent boundaries, filters out-of-domain critiques. Used by wave-executor and review-panel to prevent conflicts. |
Authority Enforcer
Validates and enforces agent authority boundaries during wave execution and review panels. Prevents agent conflicts by ensuring domain ownership is respected.
Section 1: Authority Loading
Load and parse the authority matrix to establish domain ownership rules.
Step 1: Load Authority Matrix
Input: None (uses hardcoded path)
Output: AuthorityMatrix object
Procedure:
1. Read `.planning/config/authority-matrix.yaml`
2. Parse YAML into structured object
3. Store in memory: agent_id → AgentConfig mapping
4. Build reverse index: domain → primary_agent_id
Step 2: Validate Matrix Integrity
Input: AuthorityMatrix
Output: ValidationReport
Checks:
1. All agent IDs exist in agent-registry
2. No domain is assigned to more than one agent as "exclusive"
3. Flag overlapping domains for conflict resolution
4. Verify deferred_by arrays are valid
Validation Algorithm:
def validate_matrix(matrix):
errors = []
domain_owners = {}
for agent_id, config in matrix.agents.items():
if not registry.has_agent(agent_id):
errors.append(f"Unknown agent: {agent_id}")
for domain in config.exclusive_domains:
if domain in domain_owners:
errors.append(
f"Domain conflict: '{domain}' assigned to both "
f"{domain_owners[domain]} and {agent_id}"
)
else:
domain_owners[domain] = agent_id
return ValidationReport(valid=len(errors) == 0, errors=errors)
Section 2: Boundary Validation
Check if an agent is authorized to critique or act on a specific topic.
Function: validateBoundary
Input:
agent_id: string
topic: string
active_agents: string[]
Output:
authorized: boolean
domain_owner: string|null
reason: string
Algorithm
def validate_boundary(agent_id, topic, active_agents):
if not profile.authority_enforcement:
return {
authorized: True,
domain_owner: None,
reason: "Authority enforcement disabled by control mode"
}
agent_domains = matrix.get_domains(agent_id)
if topic in agent_domains:
return {
authorized: True,
domain_owner: agent_id,
reason: "Agent has exclusive authority over this domain"
}
for active_agent in active_agents:
if active_agent == agent_id:
continue
active_domains = matrix.get_domains(active_agent)
if topic in active_domains:
return {
authorized: False,
domain_owner: active_agent,
reason: f"{active_agent} has exclusive authority; respect domain owner"
}
return {
authorized: True,
domain_owner: None,
reason: "Topic has no exclusive owner; general domain"
}
Topic Matching
Topics are matched using keyword normalization:
Normalization Rules:
- Case-insensitive matching
- Hyphens and underscores equivalent ("ci-cd" == "ci_cd")
- Substring matching for compound topics:
"api-security" matches both "api" and "security"
Specificity Scoring:
- Exact match: score 10
- Substring match: score 5
- Related domain: score 2
- No match: score 0
Section 3: Prompt Injection
Inject authority constraints into agent prompts to prevent conflicts proactively.
Function: injectAuthorityConstraints
Input:
agent_id: string
base_prompt: string
active_agents: string[]
Output:
enhanced_prompt: string
Algorithm
def inject_authority_constraints(agent_id, base_prompt, active_agents):
constraints = []
own_domains = matrix.get_domains(agent_id)
if own_domains:
constraints.append(
f"## Your Authority\\n\\n"
f"You have EXCLUSIVE AUTHORITY over these domains:\\n"
+ "\\n".join(f"- {d}" for d in own_domains)
+ "\\n\\nWhen active, other agents must respect your judgment in these areas."
)
for other_agent in active_agents:
if other_agent == agent_id:
continue
other_domains = matrix.get_domains(other_agent)
if other_domains:
agent_name = matrix.get_name(other_agent)
constraints.append(
f"\\n## Domain Ownership Required\\n\\n"
f"{agent_name} ({other_agent}) has exclusive authority over:\\n"
+ "\\n".join(f"- {d}" for d in other_domains)
+ "\\n\\nDO NOT critique or override their findings in these domains."
)
if profile.read_only:
constraints.append(
"\n## Advisory Mode Active\n\n"
"You are in ADVISORY mode. Analyze and suggest improvements "
"but DO NOT modify any files. Present your suggestions as a "
"structured list of proposed changes with rationale."
)
if profile.file_scope_restriction:
constraints.append(
"\n## Surgical Mode — File Scope Restriction\n\n"
"You may ONLY modify files explicitly listed in this plan's "
"`files_modified` field. Do not create, edit, or delete any other files. "
"If a task requires touching unlisted files, stop and escalate."
)
if not profile.human_approval_required:
pass
else:
escalation_instructions = (
"\n## Escalation Protocol\n\n"
"When you encounter a decision that falls outside your autonomous scope "
"(architecture changes, unplanned dependencies, out-of-scope files, schema changes, "
"API contract changes, deletions, infrastructure changes, or quality gate overrides), "
"you MUST use a structured escalation block in your output.\n\n"
"**Format** — wrap in `<escalation>` tags with these required fields:\n\n"
"```\n"
"<escalation>\n"
"severity: info | warning | blocker\n"
"type: architecture | dependency | scope | schema | api | deletion | infrastructure | quality\n"
"decision: What decision is needed (one sentence)\n"
"context: Why you encountered this (2-3 sentences)\n"
"alternatives: What you would do if authorized (optional)\n"
"affected_files: Files that would be modified (optional)\n"
"</escalation>\n"
"```\n\n"
"**Severity guide:**\n"
"- `info` — observation only, you can continue working\n"
"- `warning` — notable concern, you continue but it will be highlighted\n"
"- `blocker` — you MUST stop the blocked action and work on other in-scope tasks\n\n"
"**Do not skip this.** Ad-hoc text descriptions of out-of-scope issues are not "
"sufficient. The wave-executor parses `<escalation>` blocks for automated routing.\n\n"
"Reference: `.planning/config/escalation-protocol.yaml`"
)
constraints.append(escalation_instructions)
control_mode_name = profile.control_mode_name if hasattr(profile, 'control_mode_name') else "guarded"
constraints.append(
f"\n## Active Control Mode: {control_mode_name}\n\n"
f"Escalation behavior follows the `{control_mode_name}` profile. "
"See `.planning/config/escalation-protocol.yaml` for severity routing rules."
)
if constraints:
enhanced = "\\n\\n".join(constraints) + "\\n\\n---\\n\\n" + base_prompt
else:
enhanced = base_prompt
return enhanced
Example Output
## Your Authority
You have EXCLUSIVE AUTHORITY over these domains:
- security
- owasp
- vulnerability-assessment
- pentest
When active, other agents must respect your judgment in these areas.
## Domain Ownership Required
Backend Architect (engineering-backend-architect) has exclusive authority over:
- backend-architecture
- database-design
- api-design
DO NOT critique or override their findings in these domains.
---
[Original prompt content...]
Note: Constraint injection is the first line of defense. Post-execution boundary verification (Section 11) is the second line. Both are required for full authority enforcement.
Section 4: Finding Filtering
Filter findings during review synthesis to remove out-of-domain critiques.
Function: filterFindings
Input:
findings: Finding[]
active_agents: string[]
Output:
filtered_findings: Finding[]
removed_findings: Finding[]
Finding Structure
Finding:
reviewer: string
criterion: string
severity: enum
message: string
domain: string
Algorithm
def filter_findings(findings, active_agents):
if not profile.domain_filtering:
return findings, []
filtered = []
removed = []
domain_ownership = {}
for agent in active_agents:
for domain in matrix.get_domains(agent):
domain_ownership[domain] = agent
for finding in findings:
finding_domain = detect_domain(finding.criterion, finding.message)
if finding_domain in domain_ownership:
owner = domain_ownership[finding_domain]
if finding.reviewer == owner:
filtered.append(finding)
else:
if finding.severity == "BLOCKER":
filtered.append(finding)
finding.notes = (
f"[OVERRIDE] Out-of-domain BLOCKER kept per severity rule "
f"(domain owner: {owner})"
)
else:
removed.append({
**finding,
removal_reason: (
f"Out-of-domain critique filtered — "
f"{owner} is domain authority for '{finding_domain}'"
)
})
else:
filtered.append(finding)
return filtered, removed
Domain Detection
authority-enforcer is the CANONICAL OWNER of domain detection. All consumers (review-panel, review-evaluators, wave-executor) MUST delegate to detect_domain here — they must NOT reimplement or fork keyword lists.
Keyword source of truth: .planning/config/authority-matrix.yaml under each domain's keywords: field. Do NOT hardcode keyword lists in this function — load from the matrix at runtime so one schema edit propagates to every consumer.
Concrete conditions (explicit if/else — no "when appropriate"):
- IF
.planning/config/authority-matrix.yaml exists AND parses AND contains a domains: map with keywords: entries:
- Build
domain_keywords = {d.name: d.keywords for d in matrix.domains}
- ELSE (matrix missing or malformed):
- Fall back to the BUILT-IN registry below — logged as a WARN so the missing matrix is visible, never silent.
- Compute a keyword-hit score per domain against the lowercased
criterion + " " + message text.
- IF at least one domain scores > 0: return the highest-scoring domain (ties broken by domain order in the matrix — first declared wins).
- ELSE: return
"general" AND emit a DEBUG log domain_detection: no keyword match for finding "{criterion_preview}" so the "everything falls to general" failure mode from LEGION-47-174 is observable.
Built-in fallback registry (used only when the matrix is unreadable). This list MUST mirror every domain declared in authority-matrix.yaml — if the matrix adds a domain, add it here too in the same PR. No # ... etc:
BUILT_IN_DOMAIN_KEYWORDS = {
"security": [
"security", "vulnerability", "pentest", "owasp", "auth",
"authn", "authz", "encrypt", "decrypt", "sanitize", "injection",
"xss", "csrf", "ssrf", "token", "session", "cookie", "jwt",
"credential", "secret", "password", "hash", "tls", "ssl",
],
"performance": [
"performance", "optimization", "latency", "throughput", "slow",
"benchmark", "profile", "memory leak", "cpu", "hot path", "n+1",
"cache", "queue depth", "p95", "p99",
],
"accessibility": [
"accessibility", "a11y", "wcag", "screen reader", "aria",
"keyboard navigation", "color contrast", "focus ring", "alt text",
],
"api-design": [
"api", "endpoint", "rest", "graphql", "rpc", "grpc", "contract",
"schema", "openapi", "swagger", "versioning", "pagination",
],
"database": [
"database", "schema", "migration", "index", "query plan", "sql",
"nosql", "postgres", "mysql", "mongo", "transaction", "deadlock",
"foreign key", "constraint",
],
"frontend": [
"frontend", "ui", "component", "render", "hydration", "css",
"layout", "responsive", "react", "vue", "svelte", "dom", "bundle",
],
"backend": [
"backend", "service", "handler", "controller", "middleware",
"background job", "worker", "queue", "rate limit", "retry",
],
"infrastructure": [
"infrastructure", "ci", "cd", "pipeline", "deploy", "docker",
"kubernetes", "k8s", "terraform", "helm", "env var",
"configuration", "observability", "logging", "metrics", "tracing",
],
"testing": [
"test", "testing", "unit test", "integration test", "e2e",
"fixture", "mock", "stub", "coverage", "flaky",
],
"design": [
"design", "brand", "typography", "spacing", "visual hierarchy",
"design system", "token", "palette", "icon", "layout grid",
],
"marketing": [
"marketing", "campaign", "content", "copy", "seo", "growth",
"conversion", "ctr", "engagement", "audience", "channel",
],
"mobile": [
"ios", "android", "swift", "swiftui", "kotlin", "jetpack",
"react native", "flutter", "mobile", "app store", "play store",
],
}
def detect_domain(criterion, message, matrix=None, logger=None):
"""
Detect domain from criterion + message text.
Canonical owner: authority-enforcer. Consumers MUST call this function —
they must NOT reimplement keyword matching. Keyword source of truth is
.planning/config/authority-matrix.yaml (domains.<name>.keywords).
"""
text = f"{criterion} {message}".lower()
if matrix and getattr(matrix, "domains", None):
domain_keywords = {
d.name: list(d.keywords or []) for d in matrix.domains
}
else:
if logger:
logger.warn(
"authority-matrix.yaml unavailable or missing domain "
"keywords; falling back to built-in domain registry"
)
domain_keywords = BUILT_IN_DOMAIN_KEYWORDS
scores = {}
for domain, keywords in domain_keywords.items():
score = sum(1 for kw in keywords if kw and kw.lower() in text)
if score > 0:
scores[domain] = score
if scores:
ordered = list(domain_keywords.keys())
return max(scores, key=lambda d: (scores[d], -ordered.index(d)))
if logger:
preview = (criterion or "")[:80]
logger.debug(
f"domain_detection: no keyword match for finding \"{preview}\"; "
f"returning 'general'"
)
return "general"
Consumer contract (binding):
review-panel MUST call authority-enforcer.detect_domain(...). It MUST NOT define its own keyword list. Any private re-implementation is a P1 regression and must be deleted.
review-evaluators MUST call authority-enforcer.detect_domain(...) before filtering findings.
- Schema coupling: when adding a new domain, update
authority-matrix.yaml FIRST, then mirror the keyword list into BUILT_IN_DOMAIN_KEYWORDS above in the same commit. CI cross-reference check (see scripts/audit/) enforces parity.
Section 5: Integration Points
With Wave Executor
Integration Point: Agent spawning
When: Before spawning agents for a wave
Action:
1. Load authority matrix
2. Build domain ownership map from selected agents
3. For each agent:
- Load personality file
- Inject authority constraints via injectAuthorityConstraints()
- Include escalation format instructions (from escalation-protocol.yaml)
- Spawn with enhanced prompt
Integration Point: Post-execution boundary verification
When: Immediately after agent completes a task (before escalation detection)
Action:
1. Run Section 11 Post-Execution Boundary Check
2. Record result (PASS/WARN/ENFORCED) in execution tracker
3. If ENFORCED, revert out-of-scope files before proceeding
4. If WARN, append violation details to SUMMARY.md
Reference: Section 11 of this skill
Integration Point: Escalation detection
When: After agent completes a task (wave-executor Section 5.5)
Action:
1. Wave-executor scans agent output for <escalation> blocks
2. Also processes any escalation blocks generated by Section 11 boundary verification
3. Validates escalation block format against escalation-protocol.yaml
4. Routes by severity according to control mode behavior
5. Records escalations in SUMMARY.md Escalations section
Reference: .planning/config/escalation-protocol.yaml
With Review Panel
Integration Point: Finding synthesis
When: After all reviewers submit findings
Action:
1. Collect all findings from reviewers
2. Get list of agents on review panel
3. Call filterFindings() to remove out-of-domain critiques
4. Log removed findings for transparency
5. Synthesize remaining findings into final report
With Agent Registry
Integration Point: Team assembly
When: Building team for a task
Action:
1. Query agent-registry for candidate agents
2. Check for domain conflicts in proposed team
3. Warn if multiple agents claim same exclusive domain
4. Suggest resolution based on specificity hierarchy
Section 6: Conflict Resolution
Resolving Domain Conflicts
When two agents both claim authority over a topic:
Resolution Steps:
1. Check Specificity Hierarchy:
- More specific domain beats general domain
- Example: "laravel" beats "backend-architecture"
2. Check Division Priority:
- Testing-division overrides for verification topics
- Engineering-division overrides for implementation topics
3. Default to Explicit Assignment:
- Use authority-matrix.yaml as source of truth
- If still ambiguous, require human decision
Logging and Transparency
All authority decisions MUST be logged:
- Timestamp
- Active agents
- Topic in question
- Decision (authorized/domain-owned)
- Domain owner (if applicable)
- Reason
Log Location: `.planning/logs/authority-decisions-{date}.log`
Section 7: Error Handling
Common Errors
| Error | Cause | Resolution |
|---|
| Unknown agent in matrix | Agent ID typo or removed agent | Check agent-registry, update matrix |
| Duplicate domain assignment | Two agents claim same exclusive domain | Assign to more specific agent, mark other as secondary |
| Missing authority-matrix.yaml | File deleted or moved | Regenerate from template |
| Circular domain ownership | Agent A yields ownership to B while B yields ownership to A | Fix deferred_by arrays in matrix |
Validation Command
node bin/gsd-tools.cjs validate-authority-matrix
Section 8: Usage Example
Complete Workflow
const matrix = loadAuthorityMatrix('.planning/config/authority-matrix.yaml');
const validation = validateMatrix(matrix);
if (!validation.valid) {
throw new Error(`Invalid matrix: ${validation.errors.join(', ')}`);
}
const activeAgents = [
'engineering-security-engineer',
'engineering-backend-architect',
'testing-qa-verification-specialist'
];
const modeProfile = resolvedSettings.modeProfile;
for (const agentId of activeAgents) {
const basePrompt = loadAgentPersonality(agentId);
const enhancedPrompt = injectAuthorityConstraints(
agentId,
basePrompt,
activeAgents,
modeProfile
);
}
const findings = collectFindingsFromReviewers();
const { filtered, removed } = filterFindings(findings, activeAgents);
logRemovedFindings(removed);
const report = synthesizeFindings(filtered);
Section 9: Maintenance
When to Update This Skill
- New agent added: Update matrix loading, add domain mappings
- Agent domains changed: Update validation logic
- New conflict pattern identified: Add resolution rule
- Integration point changed: Update Section 5
Version History
| Version | Date | Changes |
|---|
| 1.0 | 2026-03-05 | Initial authority enforcer with boundary validation, prompt injection, and finding filtering |
| 1.1 | 2026-03-06 | Added Mode Profile Loading (Section 10) — control mode flag integration |
| 1.2 | 2026-03-31 | Added Post-Execution Boundary Verification (Section 11) — runtime enforcement after agent execution |
Section 10: Mode Profile Loading
Receive the pre-resolved control mode profile from workflow-common-core's Settings Resolution Protocol. The profile is resolved ONCE at invocation start and passed to all authority-enforcer calls — the authority-enforcer does NOT read control-modes.yaml directly.
Input
The resolved profile is a set of 5 boolean flags passed by the caller (wave-executor or review-panel):
| Flag | Type | Effect on Authority Enforcer |
|---|
authority_enforcement | boolean | When false, skip Section 2 (Boundary Validation) entirely — all agents are authorized |
domain_filtering | boolean | When false, skip Section 4 (Finding Filtering) — all findings are kept regardless of domain ownership |
human_approval_required | boolean | When false, suppress escalation prompts in Section 3 (Prompt Injection) — agents do not remind about human approval |
file_scope_restriction | boolean | When true, add file-scope constraint to Section 3 injected prompts — agents may ONLY modify files in plan files_modified |
read_only | boolean | When true, add read-only constraint to Section 3 injected prompts — agents suggest but do not execute changes |
Resolution Fallback
If no profile is provided (caller does not pass it), default to the guarded profile:
authority_enforcement: true, domain_filtering: true, human_approval_required: true
file_scope_restriction: false, read_only: false
This ensures backward compatibility — existing callers (including review-panel) that do not yet pass a profile get the same behavior as before.
Flag Consumption Pattern
All earlier sections check flags before executing their logic:
Section 2 (Boundary Validation):
if not profile.authority_enforcement: return { authorized: true, reason: "authority enforcement disabled by control mode" }
Section 3 (Prompt Injection):
if profile.read_only: append "CONSTRAINT: You are in advisory mode. Suggest changes but do NOT modify any files."
if profile.file_scope_restriction: append "CONSTRAINT: You may ONLY modify files listed in the plan's files_modified field."
if not profile.human_approval_required: omit the escalation protocol reminder from injected constraints
Section 4 (Finding Filtering):
if not profile.domain_filtering: return all findings unfiltered
Section 11 (Post-Execution Boundary Verification):
if control_mode is "autonomous" or "advisory": skip verification entirely
Section 11: Post-Execution Boundary Verification
Runtime enforcement that verifies agents stayed within their authorized scope after execution completes. Called by wave-executor after each agent finishes.
When This Runs
This verification runs after every agent execution during /legion:build, regardless of control mode. The enforcement action differs by mode:
| Control Mode | Detection | Action on Violation |
|---|
autonomous | Skip verification entirely | None |
guarded | Detect out-of-scope file modifications | Warn in SUMMARY.md, add escalation block, continue |
advisory | Not applicable (read-only agents) | None |
surgical | Detect out-of-scope file modifications | Revert out-of-scope changes, log violation, continue with in-scope work only |
Verification Procedure
Post-Execution Boundary Check:
Input:
- agent_id: the agent that just completed
- plan: the PLAN.md that was being executed
- control_mode: from settings.json
Step 1: Skip check if not applicable
- If control_mode is "autonomous" or "advisory": return PASS, skip all checks
- If plan has no files_modified field: return PASS with warning "Plan lacks files_modified — cannot verify boundaries"
Step 2: Detect actual file modifications
- Run: git diff --name-only HEAD (or compare against pre-execution snapshot)
- Collect: list of files actually modified by this agent's execution
Step 3: Compare against authorized scope
- authorized_files = plan.files_modified (from PLAN.md frontmatter)
- actual_files = files detected in Step 2
- out_of_scope = actual_files - authorized_files (set difference)
- If out_of_scope is empty: return PASS
Step 4: Classify violations
For each out-of-scope file:
- severity = "high" if file is in plan.files_forbidden
- severity = "high" if file is a config/infrastructure file (.env, CI/CD, package.json)
- severity = "medium" if file is a source file outside the plan's scope
- severity = "low" if file is a test file or documentation file
Step 5: Enforce based on control mode
If control_mode is "guarded":
a. Log violation to SUMMARY.md:
## ⚠️ Boundary Violation Detected
Agent `{agent_id}` modified files outside authorized scope:
| File | Severity | In files_forbidden? |
|------|----------|-------------------|
| {file} | {severity} | {yes/no} |
Authorized scope: {files_modified list}
b. Add escalation block:
<escalation>
severity: warning
type: scope
decision: Agent {agent_id} modified {count} files outside plan scope
context: The following files were not in the plan's files_modified list: {file_list}. Review these changes and decide whether to keep or revert them.
affected_files: {file_list}
</escalation>
c. Do NOT revert — changes are preserved for human review
d. Return WARN
If control_mode is "surgical":
a. For each out-of-scope file:
- Run: git checkout HEAD -- {file} (revert to pre-execution state)
b. Log reversion to SUMMARY.md:
## 🔒 Surgical Mode: Out-of-Scope Changes Reverted
Agent `{agent_id}` attempted to modify files outside authorized scope.
Reverted {count} files: {file_list}
In-scope changes preserved: {authorized_files that were modified}
c. Return ENFORCED
Step 6: Return result
- PASS: no violations detected
- WARN: violations detected, logged, preserved (guarded mode)
- ENFORCED: violations detected, reverted (surgical mode)
Integration with Wave Executor
The wave-executor calls this procedure at a specific point in its execution flow:
Wave Execution Flow (updated):
1. Inject agent personality (existing)
2. Inject authority constraints (existing Section 3)
3. Execute agent task (existing)
4. >>> NEW: Run Post-Execution Boundary Check <<<
5. Record execution results (existing)
6. Store outcome to memory (existing)
The boundary check result (PASS/WARN/ENFORCED) is included in the execution tracker's outcome record. WARN results increment the importance score by +1 (cross-scope work signal). ENFORCED results set outcome to "partial" (some work was discarded).
Edge Cases
- Agent creates new files not in files_modified: Treated as out-of-scope. New files not in the authorized list are violations.
- Agent modifies files that are in files_forbidden: Always severity "high" regardless of control mode. In guarded mode, this escalation is severity "blocker" not "warning".
- Git diff unavailable: If git is not available or the diff command fails, log a warning and return PASS. Never block execution due to verification infrastructure failure.
- Pre-execution snapshot unavailable: If the wave-executor did not capture a pre-execution state, fall back to
git diff --name-only HEAD which compares against the last commit. This may include changes from prior agents in the same wave — acceptable as a best-effort check.