SDS-000: Authority Sod Hooks

Authority & Separation-of-Duties Hooks Implementation

Version: 1.0.0
Status: Draft
Date: 2025-12-31
Context: shared/governance
Satisfies: SDS-031, REF-012, EU AI Act Article 14


Purpose

This specification defines the runtime hooks that enforce authority boundaries and separation-of-duties (SoD) rules per SDS-031 within the GovernedSpeed™ Governance Runtime (SDS-047).


Architecture

sequenceDiagram
    participant User
    participant PolicyGateway
    participant AuthorityHook
    participant RBACStore
    participant SoDValidator
    participant AuditLog

    User->>PolicyGateway: RequestWaiver(invariant_id, rationale)
    PolicyGateway->>AuthorityHook: validate_authority(user, "waiver.request")
    AuthorityHook->>RBACStore: get_user_roles(user)
    RBACStore-->>AuthorityHook: [R-DEV]
    AuthorityHook-->>PolicyGateway: AuthorityValid(requires_approval: true)
    
    PolicyGateway->>PolicyGateway: create_waiver_request(status: pending)
    PolicyGateway->>AuditLog: log_event(waiver.requested)
    PolicyGateway-->>User: WaiverReceipt(id, status: pending)
    
    Note over User: Approval workflow triggered
    
    User->>PolicyGateway: ApproveWaiver(waiver_id)
    PolicyGateway->>AuthorityHook: validate_authority(user, "waiver.approve")
    AuthorityHook->>RBACStore: get_user_roles(user)
    RBACStore-->>AuthorityHook: [R-AG]
    
    AuthorityHook->>SoDValidator: check_sod(requester, approver)
    SoDValidator-->>AuthorityHook: SoDValid(requester != approver)
    
    AuthorityHook-->>PolicyGateway: AuthorityValid(sod_check: passed)
    PolicyGateway->>PolicyGateway: update_waiver(status: approved)
    PolicyGateway->>AuditLog: log_event(waiver.approved, approver)
    PolicyGateway-->>User: WaiverApproved(valid_until)

Hook Types

1. PreAuthorizationHook

Triggers: Before any privileged action

Interface:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class PreAuthorizationHook:
    def validate(
        self,
        principal: Principal,
        action: Action,
        resource: Resource,
        context: RequestContext
    ) -> AuthorizationDecision:
        """
        Validates if principal has authority to perform action.
        
        Args:
            principal: User or service identity
            action: Requested action (e.g., "waiver.approve")
            resource: Target resource (e.g., waiver_id)
            context: Request context (environment, timestamp)
            
        Returns:
            AuthorizationDecision with allow/deny + reasons
            
        Raises:
            UnauthorizedError: If principal lacks required role
        """
        pass

RBAC Check:

1
2
3
4
5
6
7
8
9
10
11
12
# Example: Waiver approval requires R-AG or R-SO
def validate_waiver_approval(principal, waiver_id):
    required_roles = ["R-AG", "R-SO"]
    user_roles = rbac_store.get_roles(principal)
    
    if not any(role in required_roles for role in user_roles):
        return AuthorizationDecision(
            allowed=False,
            reason=f"Requires one of: {required_roles}"
        )
    
    return AuthorizationDecision(allowed=True)

2. SeparationOfDutiesHook

Triggers: Before committing transactions requiring two-party control

Interface:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class SeparationOfDutiesHook:
    def validate(
        self,
        transaction: Transaction,
        environment: Environment
    ) -> SoDValidation:
        """
        Validates separation-of-duties constraints.
        
        Args:
            transaction: Transaction with proposer + approver
            environment: deployment environment (staging, production)
            
        Returns:
            SoDValidation with pass/fail + violated_rules
            
        Raises:
            SoDViolationError: If SoD rule is violated
        """
        pass

SoD Rules (per SDS-031 §3):

SoD ID Rule Enforcement
SOD-01 proposer ≠ approver (production) CI gate + runtime
SOD-02 debt_requester ≠ debt_acceptor Ledger validation
SOD-03 breakglass_requester ≠ approver Security Officer approval
SOD-04 key_generator ≠ key_approver Two-party key control
SOD-05 minter ≠ semantic_owner Domain Steward cannot self-mint

Implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def validate_sod(transaction, environment):
    if environment != "production":
        return SoDValidation(passed=True)  # Relaxed in non-prod
    
    # SOD-01: Self-approval check
    if transaction.proposer == transaction.approver:
        return SoDValidation(
            passed=False,
            violated_rule="SOD-01",
            reason="Self-approval not permitted in production"
        )
    
    # SOD-03: Break-glass requires Security Officer
    if transaction.type == "break-glass":
        if "R-SO" not in get_roles(transaction.approver):
            return SoDValidation(
                passed=False,
                violated_rule="SOD-03",
                reason="Break-glass approval requires Security Officer"
            )
    
    return SoDValidation(passed=True)

3. AuditTrailHook

Triggers: After every privileged action

Interface:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class AuditTrailHook:
    def record(
        self,
        event: AuditEvent,
        immutable: bool = True
    ) -> AuditReceipt:
        """
        Records audit event in immutable ledger.
        
        Args:
            event: Audit event with actor, action, resource, timestamp
            immutable: If True, anchor to IFL for tamper-evidence
            
        Returns:
            AuditReceipt with event_id and ifl_anchor_id
        """
        pass

Audit Event Schema:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
  "event_id": "ae-01932e5f-8c7a-7b6c-9d8e-0f1a2b3c4d5e",
  "event_type": "waiver.approved",
  "actor": {
    "principal_id": "user-alice",
    "roles": ["R-AG"]
  },
  "action": "approve_waiver",
  "resource": {
    "type": "waiver",
    "id": "W-2025-001"
  },
  "context": {
    "environment": "production",
    "ip_address": "10.0.1.42",
    "timestamp": "2025-12-31T12:34:56Z"
  },
  "decision": {
    "allowed": true,
    "sod_check": "passed"
  },
  "ifl_anchor_id": "tx-2025123112345678"
}

Integration with SDS-047

Waiver Workflow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# Step 1: Request Waiver (R-DEV can request)
@requires_role("R-DEV", "R-AG")
def request_waiver(invariant_id, rationale, valid_until):
    waiver = Waiver(
        id=generate_waiver_id(),
        invariant_id=invariant_id,
        requested_by=current_user(),
        rationale=rationale,
        valid_until=valid_until,
        status="pending"
    )
    
    waiver_repo.save(waiver)
    audit_hook.record(AuditEvent("waiver.requested", waiver))
    
    return WaiverReceipt(waiver.id, status="pending")

# Step 2: Approve Waiver (R-AG or R-SO required, SoD enforced)
@requires_role("R-AG", "R-SO")
@enforce_sod(rule="SOD-WAIVER")
def approve_waiver(waiver_id):
    waiver = waiver_repo.find_by_id(waiver_id)
    
    # SoD check
    if waiver.requested_by == current_user():
        raise SoDViolationError("Cannot approve own waiver request")
    
    waiver.status = "approved"
    waiver.approved_by = current_user()
    waiver.approved_at = now()
    
    waiver_repo.update(waiver)
    audit_hook.record(AuditEvent("waiver.approved", waiver), immutable=True)
    
    return WaiverApproved(waiver.id, waiver.valid_until)

Configuration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# config/authority-hooks.yaml
rbac:
  roles:
    - id: R-DS
      name: Domain Steward
      permissions:
        - semantic.change.approve_non_breaking
        - semantic_debt.accept
    - id: R-AG
      name: Architecture Governor
      permissions:
        - semantic.change.approve_breaking
        - calm.compliance.override
        - waiver.approve
    - id: R-SO
      name: Security Officer
      permissions:
        - breakglass.approve
        - key.rotation.approve
        - waiver.approve
    - id: R-DEV
      name: Developer
      permissions:
        - semantic.change.propose
        - waiver.request

sod_rules:
  - id: SOD-01
    name: Production Self-Approval Ban
    applies_to: [waiver, semantic_change]
    environments: [production]
    constraint: proposer != approver
    
  - id: SOD-03
    name: Break-glass Security Officer Approval
    applies_to: [breakglass]
    constraint: approver.role == R-SO

audit:
  immutable_events:
    - waiver.approved
    - waiver.rejected
    - breakglass.activated
    - key.rotated
  ifl_anchoring: true
  retention_days: 2555  # 7 years for compliance

Testing Strategy

Unit Tests

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def test_sod_violation_self_approval():
    transaction = Transaction(
        proposer="alice",
        approver="alice",
        type="waiver"
    )
    
    result = sod_hook.validate(transaction, environment="production")
    
    assert result.passed == False
    assert result.violated_rule == "SOD-01"

def test_waiver_approval_requires_ag_role():
    principal = Principal(id="bob", roles=["R-DEV"])
    
    with pytest.raises(UnauthorizedError):
        auth_hook.validate(principal, action="waiver.approve", ...)

Integration Tests

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def test_end_to_end_waiver_workflow():
    # Alice (R-DEV) requests waiver
    alice = authenticate("alice", roles=["R-DEV"])
    waiver = request_waiver(
        invariant_id="INV-PERF-001",
        rationale="Batch processing requires > 100ms",
        valid_until="2026-01-31T23:59:59Z"
    )
    assert waiver.status == "pending"
    
    # Bob (R-AG) approves waiver (SoD satisfied)
    bob = authenticate("bob", roles=["R-AG"])
    approval = approve_waiver(waiver.id)
    assert approval.status == "approved"
    
    # Verify audit trail
    events = audit_log.query(waiver_id=waiver.id)
    assert len(events) == 2  # requested + approved
    assert events[1].ifl_anchor_id is not None

Deployment

Python Package

1
pip install sea-authority-hooks

Usage in SDS-047 Policy Gateway

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from sea_authority_hooks import (
    PreAuthorizationHook,
    SeparationOfDutiesHook,
    AuditTrailHook
)

# Initialize hooks
auth_hook = PreAuthorizationHook.from_config("authority-hooks.yaml")
sod_hook = SeparationOfDutiesHook.from_config("authority-hooks.yaml")
audit_hook = AuditTrailHook.from_config("authority-hooks.yaml")

# Wire into Policy Gateway
app.register_pre_hook("authorization", auth_hook)
app.register_pre_hook("sod_validation", sod_hook)
app.register_post_hook("audit", audit_hook)


Revision History

Version Date Changes
1.0.0 2025-12-31 Initial specification