aethergovern 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aethergovern/__init__.py +30 -0
- aethergovern/__version__.py +9 -0
- aethergovern/accountability/__init__.py +17 -0
- aethergovern/accountability/records.py +153 -0
- aethergovern/authorization/__init__.py +23 -0
- aethergovern/authorization/certificates.py +446 -0
- aethergovern/cli.py +142 -0
- aethergovern/dispute/__init__.py +11 -0
- aethergovern/dispute/resolution.py +132 -0
- aethergovern/models.py +334 -0
- aethergovern/policy/__init__.py +11 -0
- aethergovern/policy/enforcement.py +162 -0
- aethergovern/py.typed +0 -0
- aethergovern/revocation/__init__.py +15 -0
- aethergovern/revocation/frontier.py +75 -0
- aethergovern/simulation/__init__.py +25 -0
- aethergovern/simulation/network.py +172 -0
- aethergovern/simulation/node.py +152 -0
- aethergovern/simulation/testbed.py +151 -0
- aethergovern-2.0.0.dist-info/METADATA +695 -0
- aethergovern-2.0.0.dist-info/RECORD +26 -0
- aethergovern-2.0.0.dist-info/WHEEL +5 -0
- aethergovern-2.0.0.dist-info/entry_points.txt +2 -0
- aethergovern-2.0.0.dist-info/licenses/AUTHORS.md +61 -0
- aethergovern-2.0.0.dist-info/licenses/LICENSE +26 -0
- aethergovern-2.0.0.dist-info/top_level.txt +1 -0
aethergovern/cli.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AetherGovern — Command-line interface (v2.0.0).
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
python -m aethergovern.cli info
|
|
6
|
+
python -m aethergovern.cli verify
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
from aethergovern.__version__ import (
|
|
14
|
+
__version__,
|
|
15
|
+
__author__,
|
|
16
|
+
__email__,
|
|
17
|
+
__doi__,
|
|
18
|
+
__license__,
|
|
19
|
+
__paper_version__,
|
|
20
|
+
__record_id__,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def cmd_info(args) -> int:
|
|
25
|
+
"""Print version, DOI, and record information."""
|
|
26
|
+
print("=" * 60)
|
|
27
|
+
print(f"AetherGovern v{__version__}")
|
|
28
|
+
print("=" * 60)
|
|
29
|
+
print(f"Author: {__author__}")
|
|
30
|
+
print(f"Email: {__email__}")
|
|
31
|
+
print(f"License: {__license__}")
|
|
32
|
+
print(f"Paper version: {__paper_version__}")
|
|
33
|
+
print(f"Record ID: {__record_id__}")
|
|
34
|
+
print(f"DOI: {__doi__}")
|
|
35
|
+
print(f"DOI URL: https://doi.org/{__doi__}")
|
|
36
|
+
print()
|
|
37
|
+
print("AetherGovern is the fifth functional dimension of the Aether family:")
|
|
38
|
+
print(" AetherGit (storage) — 10.5281/zenodo.23966896")
|
|
39
|
+
print(" AetherFlow (integration) — 10.5281/zenodo.23979129")
|
|
40
|
+
print(" AetherProof (verification) — 10.5281/zenodo.24013124")
|
|
41
|
+
print(" AetherMesh (decentralization) — 10.5281/zenodo.24053492")
|
|
42
|
+
print(" AetherGovern (governance) — 10.5281/zenodo.24054524")
|
|
43
|
+
print()
|
|
44
|
+
print("Status: Architectural Hypothesis Under Investigation (v0.1.0).")
|
|
45
|
+
print(" No real-world deployment. Theorems P1–P2 are proof sketches.")
|
|
46
|
+
print("=" * 60)
|
|
47
|
+
return 0
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def cmd_verify(args) -> int:
|
|
51
|
+
"""Run a quick self-check."""
|
|
52
|
+
print("=" * 60)
|
|
53
|
+
print(f"AetherGovern v{__version__} — self-check")
|
|
54
|
+
print("=" * 60)
|
|
55
|
+
|
|
56
|
+
ok = True
|
|
57
|
+
|
|
58
|
+
# 1. Ed25519 signature
|
|
59
|
+
print("[1/3] Ed25519 signature verification")
|
|
60
|
+
from aethergovern.authorization.certificates import generate_keypair
|
|
61
|
+
priv, pub = generate_keypair()
|
|
62
|
+
msg = b"test message"
|
|
63
|
+
sig = priv.sign(msg)
|
|
64
|
+
try:
|
|
65
|
+
pub.verify(sig, msg)
|
|
66
|
+
print(" ✅ Valid signature verified")
|
|
67
|
+
except Exception:
|
|
68
|
+
print(" ❌ Signature verification failed")
|
|
69
|
+
ok = False
|
|
70
|
+
|
|
71
|
+
# 2. Frontier join
|
|
72
|
+
print("[2/3] Revocation frontier join")
|
|
73
|
+
from aethergovern.models import RevocationFrontier
|
|
74
|
+
from aethergovern.revocation.frontier import join_frontiers
|
|
75
|
+
f1 = RevocationFrontier("f1", frozenset(["r1", "r2"]), "2026-11-09T00:00:00Z")
|
|
76
|
+
f2 = RevocationFrontier("f2", frozenset(["r2", "r3"]), "2026-11-09T00:00:00Z")
|
|
77
|
+
joined = join_frontiers(f1, f2)
|
|
78
|
+
if joined.revocations == frozenset(["r1", "r2", "r3"]):
|
|
79
|
+
print(f" ✅ Frontier join: {sorted(joined.revocations)}")
|
|
80
|
+
else:
|
|
81
|
+
print(f" ❌ Frontier join failed: {joined.revocations}")
|
|
82
|
+
ok = False
|
|
83
|
+
|
|
84
|
+
# 3. Policy evaluation
|
|
85
|
+
print("[3/3] Policy evaluation")
|
|
86
|
+
from aethergovern.models import (
|
|
87
|
+
Action, ActionKind, Policy, PolicyStatus, PolicyVerdict, Rule
|
|
88
|
+
)
|
|
89
|
+
from aethergovern.policy.enforcement import apply_policy, policy_hash
|
|
90
|
+
policy = Policy(
|
|
91
|
+
policy_id="p1", version=1,
|
|
92
|
+
rules=[Rule("commit", "branch/main", PolicyVerdict.ALLOW)],
|
|
93
|
+
approval_chain=["agent-1"],
|
|
94
|
+
status=PolicyStatus.ENFORCED,
|
|
95
|
+
created_at="2026-11-01T00:00:00Z",
|
|
96
|
+
effective_from="2026-11-01T00:00:00Z",
|
|
97
|
+
)
|
|
98
|
+
action = Action(
|
|
99
|
+
action_id="a"*64, kind=ActionKind.COMMIT, actor="agent-1",
|
|
100
|
+
resource="branch/main", payload={},
|
|
101
|
+
timestamp="2026-11-09T00:00:00Z",
|
|
102
|
+
frontier_ref="f"*64, policy_ref=policy_hash(policy),
|
|
103
|
+
authority_chain=["c"*64], signature=b"\x00"*64,
|
|
104
|
+
)
|
|
105
|
+
verdict = apply_policy(action, policy)
|
|
106
|
+
if verdict == PolicyVerdict.ALLOW:
|
|
107
|
+
print(f" ✅ Policy verdict: {verdict.value}")
|
|
108
|
+
else:
|
|
109
|
+
print(f" ❌ Policy verdict: {verdict.value}")
|
|
110
|
+
ok = False
|
|
111
|
+
|
|
112
|
+
print("=" * 60)
|
|
113
|
+
if ok:
|
|
114
|
+
print(" ✅ All checks passed")
|
|
115
|
+
return 0
|
|
116
|
+
else:
|
|
117
|
+
print(" ❌ Some checks failed")
|
|
118
|
+
return 1
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def main(argv=None) -> int:
|
|
122
|
+
"""Main entry point."""
|
|
123
|
+
import argparse
|
|
124
|
+
parser = argparse.ArgumentParser(
|
|
125
|
+
prog="aethergovern",
|
|
126
|
+
description=f"AetherGovern v{__version__} — falsification-first governance",
|
|
127
|
+
)
|
|
128
|
+
parser.add_argument("--version", action="version", version=f"aethergovern {__version__}")
|
|
129
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
130
|
+
|
|
131
|
+
p_info = sub.add_parser("info", help="Print version and record info")
|
|
132
|
+
p_info.set_defaults(func=cmd_info)
|
|
133
|
+
|
|
134
|
+
p_verify = sub.add_parser("verify", help="Run a self-check")
|
|
135
|
+
p_verify.set_defaults(func=cmd_verify)
|
|
136
|
+
|
|
137
|
+
args = parser.parse_args(argv)
|
|
138
|
+
return args.func(args)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
if __name__ == "__main__":
|
|
142
|
+
sys.exit(main())
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AetherGovern — Dispute resolution (Layer 5, v2.0.0).
|
|
3
|
+
|
|
4
|
+
Implements:
|
|
5
|
+
- escalate: escalate an unresolved dispute to human review
|
|
6
|
+
- resolve_by_rule: resolve a dispute by a deterministic rule
|
|
7
|
+
|
|
8
|
+
Reference:
|
|
9
|
+
Baladi, S. (2026). AetherGovern v2.0.0,
|
|
10
|
+
docs/specification/02_operations.md, Operation 6 (escalate).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from datetime import datetime, timezone
|
|
16
|
+
from typing import Dict
|
|
17
|
+
|
|
18
|
+
from aethergovern.models import (
|
|
19
|
+
Dispute,
|
|
20
|
+
DisputeResolution,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# =============================================================================
|
|
25
|
+
# Time helpers
|
|
26
|
+
# =============================================================================
|
|
27
|
+
|
|
28
|
+
def _now_iso() -> str:
|
|
29
|
+
return datetime.now(timezone.utc).isoformat()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# =============================================================================
|
|
33
|
+
# escalate
|
|
34
|
+
# =============================================================================
|
|
35
|
+
|
|
36
|
+
def escalate(
|
|
37
|
+
dispute: Dispute,
|
|
38
|
+
reason: str,
|
|
39
|
+
escalation_target: str,
|
|
40
|
+
) -> Dispute:
|
|
41
|
+
"""
|
|
42
|
+
Escalate an unresolved dispute to human review.
|
|
43
|
+
|
|
44
|
+
Preconditions:
|
|
45
|
+
- dispute.resolution == PENDING or ESCALATED
|
|
46
|
+
- escalation_target is a non-empty string
|
|
47
|
+
|
|
48
|
+
Postconditions:
|
|
49
|
+
- result.resolution == ESCALATED
|
|
50
|
+
- result.escalation_record contains reason and escalation_target
|
|
51
|
+
|
|
52
|
+
Invariants:
|
|
53
|
+
I9.2 — Escalation completeness: escalation_record must be non-empty.
|
|
54
|
+
I9.3 — No silent resolution: the dispute is explicitly escalated.
|
|
55
|
+
"""
|
|
56
|
+
if dispute.resolution == DisputeResolution.RESOLVED_BY_RULE:
|
|
57
|
+
raise ValueError(
|
|
58
|
+
f"dispute {dispute.dispute_id} is already resolved by rule "
|
|
59
|
+
f"(rule={dispute.precedence_rule})"
|
|
60
|
+
)
|
|
61
|
+
if not escalation_target:
|
|
62
|
+
raise ValueError("empty escalation target")
|
|
63
|
+
if not reason:
|
|
64
|
+
raise ValueError("empty escalation reason")
|
|
65
|
+
|
|
66
|
+
escalation_record: Dict = {
|
|
67
|
+
"reason": reason,
|
|
68
|
+
"escalation_target": escalation_target,
|
|
69
|
+
"escalated_at": _now_iso(),
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return Dispute(
|
|
73
|
+
dispute_id=dispute.dispute_id,
|
|
74
|
+
claim_a=dispute.claim_a,
|
|
75
|
+
claim_b=dispute.claim_b,
|
|
76
|
+
precedence_rule=dispute.precedence_rule,
|
|
77
|
+
resolution=DisputeResolution.ESCALATED,
|
|
78
|
+
escalation_record=escalation_record,
|
|
79
|
+
created_at=dispute.created_at,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# =============================================================================
|
|
84
|
+
# resolve_by_rule
|
|
85
|
+
# =============================================================================
|
|
86
|
+
|
|
87
|
+
def resolve_by_rule(dispute: Dispute, rule: str) -> Dispute:
|
|
88
|
+
"""
|
|
89
|
+
Resolve a dispute by a deterministic precedence rule.
|
|
90
|
+
|
|
91
|
+
Supported rules (for v2.0.0):
|
|
92
|
+
- "more_restrictive": the claim with the smaller scope wins.
|
|
93
|
+
- "later_expiry": the claim with the later expiry wins
|
|
94
|
+
(approximated by chain length for v2.0.0).
|
|
95
|
+
|
|
96
|
+
Preconditions:
|
|
97
|
+
- dispute.resolution == PENDING
|
|
98
|
+
- rule is a non-empty string
|
|
99
|
+
|
|
100
|
+
Postconditions:
|
|
101
|
+
- result.resolution == RESOLVED_BY_RULE
|
|
102
|
+
- result.precedence_rule == rule
|
|
103
|
+
|
|
104
|
+
Invariants:
|
|
105
|
+
I9.1 — Determinism: the same input always gives the same resolution.
|
|
106
|
+
I9.3 — No silent resolution: the rule is explicitly recorded.
|
|
107
|
+
"""
|
|
108
|
+
if dispute.resolution == DisputeResolution.RESOLVED_BY_RULE:
|
|
109
|
+
raise ValueError(
|
|
110
|
+
f"dispute {dispute.dispute_id} is already resolved "
|
|
111
|
+
f"(rule={dispute.precedence_rule})"
|
|
112
|
+
)
|
|
113
|
+
if dispute.resolution == DisputeResolution.ESCALATED:
|
|
114
|
+
raise ValueError(
|
|
115
|
+
f"dispute {dispute.dispute_id} has been escalated; "
|
|
116
|
+
f"cannot resolve by rule"
|
|
117
|
+
)
|
|
118
|
+
if not rule:
|
|
119
|
+
raise ValueError("empty rule")
|
|
120
|
+
|
|
121
|
+
# Deterministic resolution: the rule determines the winner.
|
|
122
|
+
# For v2.0.0, we only record the rule; the winner is determined by the
|
|
123
|
+
# caller (or a future implementation of the rule).
|
|
124
|
+
return Dispute(
|
|
125
|
+
dispute_id=dispute.dispute_id,
|
|
126
|
+
claim_a=dispute.claim_a,
|
|
127
|
+
claim_b=dispute.claim_b,
|
|
128
|
+
precedence_rule=rule,
|
|
129
|
+
resolution=DisputeResolution.RESOLVED_BY_RULE,
|
|
130
|
+
escalation_record=dispute.escalation_record,
|
|
131
|
+
created_at=dispute.created_at,
|
|
132
|
+
)
|
aethergovern/models.py
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AetherGovern — Core data models (v2.0.0).
|
|
3
|
+
|
|
4
|
+
This module defines the ten entities of AetherGovern v2.0.0:
|
|
5
|
+
|
|
6
|
+
1. Agent — actor identity
|
|
7
|
+
2. Action — operation (commit, merge, delete, deploy, rotate_key)
|
|
8
|
+
3. Resource — target (branch, path, key)
|
|
9
|
+
4. DelegationCertificate — signed grant of scope
|
|
10
|
+
5. RevocationRecord — signed withdrawal of a certificate
|
|
11
|
+
6. Policy — rule that governs authorization
|
|
12
|
+
7. RevocationFrontier — set of revocation records an actor has seen
|
|
13
|
+
8. AuthorityChain — sequence of certificates from root to leaf
|
|
14
|
+
9. Dispute — conflict between two authority claims
|
|
15
|
+
10. AccountabilityRecord — after-the-fact trace of an admitted action
|
|
16
|
+
|
|
17
|
+
Reference:
|
|
18
|
+
Baladi, S. (2026). AetherGovern: A Falsification-First Investigation
|
|
19
|
+
of Governance in the Aether Family. Zenodo.
|
|
20
|
+
https://doi.org/10.5281/zenodo.24054524
|
|
21
|
+
|
|
22
|
+
v2.0.0 Specification, docs/specification/01_entities.md.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from dataclasses import dataclass, field
|
|
28
|
+
from enum import Enum
|
|
29
|
+
from typing import Dict, FrozenSet, List, Optional, Tuple
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# =============================================================================
|
|
33
|
+
# Enums
|
|
34
|
+
# =============================================================================
|
|
35
|
+
|
|
36
|
+
class AgentStatus(str, Enum):
|
|
37
|
+
"""Status of an agent."""
|
|
38
|
+
ACTIVE = "active"
|
|
39
|
+
SUSPENDED = "suspended"
|
|
40
|
+
REVOKED = "revoked"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ActionKind(str, Enum):
|
|
44
|
+
"""Kind of action an agent may perform."""
|
|
45
|
+
COMMIT = "commit"
|
|
46
|
+
MERGE = "merge"
|
|
47
|
+
DELETE = "delete"
|
|
48
|
+
DEPLOY = "deploy"
|
|
49
|
+
ROTATE_KEY = "rotate_key"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ResourceKind(str, Enum):
|
|
53
|
+
"""Kind of resource an action targets."""
|
|
54
|
+
BRANCH = "branch"
|
|
55
|
+
PATH = "path"
|
|
56
|
+
KEY = "key"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class PolicyStatus(str, Enum):
|
|
60
|
+
"""Status of a policy."""
|
|
61
|
+
PROPOSED = "proposed"
|
|
62
|
+
APPROVED = "approved"
|
|
63
|
+
ENFORCED = "enforced"
|
|
64
|
+
REVOKED = "revoked"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class DisputeResolution(str, Enum):
|
|
68
|
+
"""Resolution status of a dispute."""
|
|
69
|
+
PENDING = "pending"
|
|
70
|
+
RESOLVED_BY_RULE = "resolved_by_rule"
|
|
71
|
+
ESCALATED = "escalated"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class PolicyVerdict(str, Enum):
|
|
75
|
+
"""Verdict of a policy evaluation."""
|
|
76
|
+
ALLOW = "allow"
|
|
77
|
+
DENY = "deny"
|
|
78
|
+
QUARANTINE = "quarantine"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# =============================================================================
|
|
82
|
+
# Type aliases
|
|
83
|
+
# =============================================================================
|
|
84
|
+
|
|
85
|
+
Scope = FrozenSet[Tuple[str, str]]
|
|
86
|
+
"""A scope is a frozen set of (action_kind, resource_id) pairs."""
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# =============================================================================
|
|
90
|
+
# Entity 1 — Agent
|
|
91
|
+
# =============================================================================
|
|
92
|
+
|
|
93
|
+
@dataclass(frozen=True)
|
|
94
|
+
class Agent:
|
|
95
|
+
"""
|
|
96
|
+
An autonomous software actor.
|
|
97
|
+
|
|
98
|
+
Invariants:
|
|
99
|
+
I1.1 — Key uniqueness (enforced by the registry, not the entity)
|
|
100
|
+
I1.2 — Status monotonicity (enforced by the state machine)
|
|
101
|
+
I1.3 — Registration completeness (enforced by the admission layer)
|
|
102
|
+
"""
|
|
103
|
+
agent_id: str
|
|
104
|
+
public_key: bytes
|
|
105
|
+
key_id: str
|
|
106
|
+
role: Optional[str]
|
|
107
|
+
registered_at: str
|
|
108
|
+
status: AgentStatus = AgentStatus.ACTIVE
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# =============================================================================
|
|
112
|
+
# Entity 2 — Action
|
|
113
|
+
# =============================================================================
|
|
114
|
+
|
|
115
|
+
@dataclass(frozen=True)
|
|
116
|
+
class Action:
|
|
117
|
+
"""
|
|
118
|
+
An operation by an agent on a resource.
|
|
119
|
+
|
|
120
|
+
Invariants:
|
|
121
|
+
I2.1 — Causal citation (enforced by the admission layer)
|
|
122
|
+
I2.2 — Content addressing (enforced by the constructor)
|
|
123
|
+
I2.3 — Signature covers content (enforced by the signature layer)
|
|
124
|
+
I2.4 — Chain completeness (enforced by the admission layer)
|
|
125
|
+
"""
|
|
126
|
+
action_id: str
|
|
127
|
+
kind: ActionKind
|
|
128
|
+
actor: str
|
|
129
|
+
resource: str
|
|
130
|
+
payload: Dict
|
|
131
|
+
timestamp: str
|
|
132
|
+
frontier_ref: str
|
|
133
|
+
policy_ref: str
|
|
134
|
+
authority_chain: List[str]
|
|
135
|
+
signature: bytes = b""
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
# =============================================================================
|
|
139
|
+
# Entity 3 — Resource
|
|
140
|
+
# =============================================================================
|
|
141
|
+
|
|
142
|
+
@dataclass(frozen=True)
|
|
143
|
+
class Resource:
|
|
144
|
+
"""
|
|
145
|
+
The target of an action.
|
|
146
|
+
|
|
147
|
+
Invariants:
|
|
148
|
+
I3.1 — Identifier stability (enforced by frozen dataclass)
|
|
149
|
+
I3.2 — Kind immutability (enforced by frozen dataclass)
|
|
150
|
+
"""
|
|
151
|
+
resource_id: str
|
|
152
|
+
kind: ResourceKind
|
|
153
|
+
owner: str
|
|
154
|
+
metadata: Dict = field(default_factory=dict)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# =============================================================================
|
|
158
|
+
# Entity 4 — DelegationCertificate
|
|
159
|
+
# =============================================================================
|
|
160
|
+
|
|
161
|
+
@dataclass(frozen=True)
|
|
162
|
+
class DelegationCertificate:
|
|
163
|
+
"""
|
|
164
|
+
A signed grant of scope from an issuer to a subject.
|
|
165
|
+
|
|
166
|
+
Invariants:
|
|
167
|
+
I4.1 — Attenuation (enforced by the delegate operation)
|
|
168
|
+
I4.2 — Root recognition (enforced by the check operation)
|
|
169
|
+
I4.3 — Expiry (enforced by the check operation)
|
|
170
|
+
I4.4 — Signature covers content (enforced by the signature layer)
|
|
171
|
+
I4.5 — No cycle (enforced by the parent_ref chain)
|
|
172
|
+
"""
|
|
173
|
+
certificate_id: str
|
|
174
|
+
issuer: str
|
|
175
|
+
subject: str
|
|
176
|
+
scope: Scope
|
|
177
|
+
constraints: Dict
|
|
178
|
+
expiry: str
|
|
179
|
+
parent_ref: Optional[str]
|
|
180
|
+
signature: bytes = b""
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
# =============================================================================
|
|
184
|
+
# Entity 5 — RevocationRecord
|
|
185
|
+
# =============================================================================
|
|
186
|
+
|
|
187
|
+
@dataclass(frozen=True)
|
|
188
|
+
class RevocationRecord:
|
|
189
|
+
"""
|
|
190
|
+
A signed withdrawal of a certificate.
|
|
191
|
+
|
|
192
|
+
Invariants:
|
|
193
|
+
I5.1 — Monotonicity (enforced by the frontier)
|
|
194
|
+
I5.2 — Signature covers content (enforced by the signature layer)
|
|
195
|
+
I5.3 — Issuer authority (enforced by the revoke operation)
|
|
196
|
+
"""
|
|
197
|
+
revocation_id: str
|
|
198
|
+
certificate_ref: str
|
|
199
|
+
issuer: str
|
|
200
|
+
reason: str
|
|
201
|
+
timestamp: str
|
|
202
|
+
signature: bytes = b""
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
# =============================================================================
|
|
206
|
+
# Entity 6 — Policy
|
|
207
|
+
# =============================================================================
|
|
208
|
+
|
|
209
|
+
@dataclass(frozen=True)
|
|
210
|
+
class Rule:
|
|
211
|
+
"""A single policy rule."""
|
|
212
|
+
action_kind: str
|
|
213
|
+
resource_pattern: str
|
|
214
|
+
verdict: PolicyVerdict
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@dataclass(frozen=True)
|
|
218
|
+
class Policy:
|
|
219
|
+
"""
|
|
220
|
+
A rule that governs authorization.
|
|
221
|
+
|
|
222
|
+
Invariants:
|
|
223
|
+
I6.1 — Version monotonicity (enforced by the policy registry)
|
|
224
|
+
I6.2 — Approval completeness (enforced by the apply_policy operation)
|
|
225
|
+
I6.3 — Effective date (enforced by the apply_policy operation)
|
|
226
|
+
I6.4 — Supersession chain (enforced by the policy registry)
|
|
227
|
+
"""
|
|
228
|
+
policy_id: str
|
|
229
|
+
version: int
|
|
230
|
+
rules: List[Rule]
|
|
231
|
+
approval_chain: List[str]
|
|
232
|
+
status: PolicyStatus
|
|
233
|
+
created_at: str
|
|
234
|
+
effective_from: str
|
|
235
|
+
supersedes: Optional[str] = None
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
# =============================================================================
|
|
239
|
+
# Entity 7 — RevocationFrontier
|
|
240
|
+
# =============================================================================
|
|
241
|
+
|
|
242
|
+
@dataclass(frozen=True)
|
|
243
|
+
class RevocationFrontier:
|
|
244
|
+
"""
|
|
245
|
+
The set of revocation records an actor has seen.
|
|
246
|
+
|
|
247
|
+
Invariants:
|
|
248
|
+
I7.1 — Grow-only (enforced by the frontier operations)
|
|
249
|
+
I7.2 — Join is union (enforced by the join method)
|
|
250
|
+
I7.3 — Content addressing (enforced by the constructor)
|
|
251
|
+
"""
|
|
252
|
+
frontier_id: str
|
|
253
|
+
revocations: FrozenSet[str]
|
|
254
|
+
created_at: str
|
|
255
|
+
|
|
256
|
+
def join(self, other: "RevocationFrontier") -> "RevocationFrontier":
|
|
257
|
+
"""Join two frontiers: set union of revocations."""
|
|
258
|
+
return RevocationFrontier(
|
|
259
|
+
frontier_id=f"{self.frontier_id}+{other.frontier_id}",
|
|
260
|
+
revocations=self.revocations | other.revocations,
|
|
261
|
+
created_at=self.created_at,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
def __le__(self, other: "RevocationFrontier") -> bool:
|
|
265
|
+
"""Partial order: self ≤ other iff self.revocations ⊆ other.revocations."""
|
|
266
|
+
return self.revocations <= other.revocations
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
# =============================================================================
|
|
270
|
+
# Entity 8 — AuthorityChain
|
|
271
|
+
# =============================================================================
|
|
272
|
+
|
|
273
|
+
@dataclass(frozen=True)
|
|
274
|
+
class AuthorityChain:
|
|
275
|
+
"""
|
|
276
|
+
A sequence of delegation certificates from a root to a leaf agent.
|
|
277
|
+
|
|
278
|
+
Invariants:
|
|
279
|
+
I8.1 — Attenuation preserved (enforced by the check operation)
|
|
280
|
+
I8.2 — Root recognized (enforced by the check operation)
|
|
281
|
+
I8.3 — No cycle (enforced by the certificate chain)
|
|
282
|
+
I8.4 — Contiguity (enforced by the check operation)
|
|
283
|
+
"""
|
|
284
|
+
chain_id: str
|
|
285
|
+
certificates: List[str]
|
|
286
|
+
leaf_agent: str
|
|
287
|
+
leaf_scope: Scope
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
# =============================================================================
|
|
291
|
+
# Entity 9 — Dispute
|
|
292
|
+
# =============================================================================
|
|
293
|
+
|
|
294
|
+
@dataclass(frozen=True)
|
|
295
|
+
class Dispute:
|
|
296
|
+
"""
|
|
297
|
+
A conflict between two authority claims.
|
|
298
|
+
|
|
299
|
+
Invariants:
|
|
300
|
+
I9.1 — Determinism (enforced by the escalate operation)
|
|
301
|
+
I9.2 — Escalation completeness (enforced by the escalate operation)
|
|
302
|
+
I9.3 — No silent resolution (enforced by the escalate operation)
|
|
303
|
+
"""
|
|
304
|
+
dispute_id: str
|
|
305
|
+
claim_a: AuthorityChain
|
|
306
|
+
claim_b: AuthorityChain
|
|
307
|
+
precedence_rule: Optional[str]
|
|
308
|
+
resolution: DisputeResolution
|
|
309
|
+
escalation_record: Optional[Dict] = None
|
|
310
|
+
created_at: str = ""
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
# =============================================================================
|
|
314
|
+
# Entity 10 — AccountabilityRecord
|
|
315
|
+
# =============================================================================
|
|
316
|
+
|
|
317
|
+
@dataclass(frozen=True)
|
|
318
|
+
class AccountabilityRecord:
|
|
319
|
+
"""
|
|
320
|
+
The after-the-fact trace of an admitted action.
|
|
321
|
+
|
|
322
|
+
Invariants:
|
|
323
|
+
I10.1 — Immutability (enforced by frozen dataclass)
|
|
324
|
+
I10.2 — Hash-link integrity (enforced by the record chain)
|
|
325
|
+
I10.3 — Attribution completeness (enforced by the admission layer)
|
|
326
|
+
I10.4 — Signature covers content (enforced by the signature layer)
|
|
327
|
+
"""
|
|
328
|
+
record_id: str
|
|
329
|
+
action_ref: str
|
|
330
|
+
authority_chain_ref: str
|
|
331
|
+
admitted_at: str
|
|
332
|
+
admitted_by: str
|
|
333
|
+
prev_record_hash: str
|
|
334
|
+
signature: bytes = b""
|