Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions SESSION_FOCUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,20 @@

*Current sprint, SDK status, and active work. Updated by operator and autonomous sessions.*

*Last updated: 2026-05-14 (Sprint 50)*
*Last updated: 2026-05-14 (Sprint 51)*

---

## Current Sprint

**See `docs/SPRINT.md` for full sprint plan and task details.** Do not duplicate sprint content here — SPRINT.md is the source of truth for task scope, status, and dependencies.

### Sprint 51 Summary: Minimum Viable Society Validation + Constraint Alignment (COMPLETE)

| Task | Status | Notes |
|------|--------|-------|
| T1: validate_minimum_viable() + Constraint alignment | DONE | Resolves Sprint 49 audit P5+P6. `validate_minimum_viable()` added to `web4/role.py`: checks 7 base-mandatory roles, ≥2 distinct fillers (operational), witnessing capacity (operational). `Constraint` dataclass aligned with Rust: `threshold: float` + `hard: bool` replacing untyped `value: Any`. 1 new export (369 total), 14 new tests (2670 total), 0 new files. JSON schema + test vectors updated. |

### Sprint 50 Summary: Add SocietyRole + RoleAssignment to Python SDK (COMPLETE)

| Task | Status | Notes |
Expand Down Expand Up @@ -196,9 +202,9 @@ See `docs/SPRINT.md` for full history. Highlights: JSON-LD serialization for all

- **Version**: 0.26.0
- **Modules**: 23 library modules + MCP server entry point (trust, lct, atp, federation, r6, mrh, acp, dictionary, entity, capability, errors, metabolic, binding, society, role, reputation, security, protocol, mcp, attestation, validation, deserialize, generate, mcp_server)
- **Tests**: 2656 passing
- **Tests**: 2670 passing
- **CLI**: `web4 info/validate/list-schemas/roundtrip/generate/selftest/trust` (7 subcommands)
- **Exports**: 368 symbols via `web4/__init__.py`
- **Exports**: 369 symbols via `web4/__init__.py`
- **from_dict()**: 58 classmethods across 10 modules — all classes with to_dict()/as_dict() have matching from_dict()
- **Dispatcher**: 23 types via `web4.from_jsonld()` (19 class-based + 3 function-based + TrustQuery)
- **Generator**: 23 types via `web4.generate()` — minimal valid JSON-LD documents
Expand Down
38 changes: 37 additions & 1 deletion docs/SPRINT.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,48 @@
# Web4 Sprint Plan

**Created**: 2026-03-14
**Updated**: 2026-05-14 (Sprint 50)
**Updated**: 2026-05-14 (Sprint 51)
**Phase**: Development
**Track**: web4 (Legion)

---

## Sprint 51: Minimum Viable Society Validation + Constraint Alignment (2026-05-14)

Resolves two remaining autonomous-actionable items from the Sprint 49
cross-language audit fix queue: P5 (validate_minimum_viable) and P6
(Constraint threshold + hard flag alignment).

### T1: validate_minimum_viable() + Constraint alignment
**Status**: DONE
**Completed**: 2026-05-14
**Authorized by**: Sprint 49 audit fix queue P5+P6. Policy-reviewed and approved.
**Scope**:
Two changes to advance cross-language parity:

1. **P5 — `validate_minimum_viable()`** (MEDIUM): New function in `web4/role.py`
implementing `inter-society-protocol.md` §6.2 semantic viability checks.
Takes a list of `RoleAssignment` and an `is_operational` flag. Checks:
(a) all 7 base-mandatory roles are filled, (b) ≥2 distinct role-filling
entities when operational, (c) Witness or Auditor role assigned when
operational. Returns list of error strings. Cross-language parity with
`web4-core/src/society.rs::validate_minimum_viable()`.

2. **P6 — Constraint alignment** (MEDIUM): Updated `Constraint` dataclass in
`web4/r6.py`: replaced untyped `value: Any` with `threshold: float` +
`hard: bool = True`, matching `web4-core/src/r6.rs::Constraint`. Updated
`to_dict()`, `from_dict()` (backward-compatible: accepts legacy `value`
key), `Rules.check_constraint()`, JSON schemas, and test vectors.

**Result**: 1 new export (369 total), 14 new tests (2670 total), 0 new files.
mypy --strict clean, ruff lint/format clean. JSON schema and test vectors
updated for new Constraint wire format.

**Remaining from audit**: P4 (MetabolicState reconciliation — needs operator
decision), P7 (SocietyState role integration — needs operator decision).

---

## Sprint 50: Add SocietyRole + RoleAssignment to Python SDK (2026-05-14)

Implements the top 3 items from the Sprint 49 cross-language audit fix queue:
Expand Down
14 changes: 7 additions & 7 deletions web4-standard/implementation/sdk/tests/test_r6.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def test_prohibition_overrides_permission(self):
def test_constraint_minimum(self):
rules = Rules(
constraints=[
Constraint(constraint_type="atp_minimum", value=50),
Constraint(constraint_type="atp_minimum", threshold=50),
]
)
assert rules.check_constraint("atp_minimum", 100)
Expand All @@ -90,7 +90,7 @@ def test_constraint_minimum(self):
def test_constraint_maximum(self):
rules = Rules(
constraints=[
Constraint(constraint_type="rate_limit", value=100),
Constraint(constraint_type="rate_limit", threshold=100),
]
)
assert rules.check_constraint("rate_limit", 50)
Expand All @@ -100,7 +100,7 @@ def test_constraint_maximum(self):
def test_no_matching_constraint(self):
rules = Rules(
constraints=[
Constraint(constraint_type="atp_minimum", value=50),
Constraint(constraint_type="atp_minimum", threshold=50),
]
)
# No constraint for "rate_limit" → passes
Expand Down Expand Up @@ -856,8 +856,8 @@ def _make_full_action(self) -> R7Action:
law_hash="sha256:governance_v2",
society="lct:web4:society:genesis",
constraints=[
Constraint(constraint_type="atp_minimum", value=50),
Constraint(constraint_type="rate_limit", value=100),
Constraint(constraint_type="atp_minimum", threshold=50),
Constraint(constraint_type="rate_limit", threshold=100),
],
permissions=["read", "analyze"],
prohibitions=["delete"],
Expand Down Expand Up @@ -1296,7 +1296,7 @@ def test_full_action_validates(self):
rules=Rules(
law_hash="sha256:abc",
society="lct:society:genesis",
constraints=[Constraint(constraint_type="atp_minimum", value=50)],
constraints=[Constraint(constraint_type="atp_minimum", threshold=50)],
permissions=["read"],
),
role=Role(
Expand Down Expand Up @@ -1394,7 +1394,7 @@ def _make_full_action(self):
rules=Rules(
law_hash="lh1",
society="soc1",
constraints=[Constraint("trust_min", 0.5)],
constraints=[Constraint("trust_min", 0.5, True)],
permissions=["read"],
prohibitions=["delete"],
),
Expand Down
23 changes: 18 additions & 5 deletions web4-standard/implementation/sdk/tests/test_r6_roundtrip.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,30 @@

class TestConstraintRoundTrip:
def test_basic(self) -> None:
c = Constraint(constraint_type="rate_limit", value=100)
c = Constraint(constraint_type="rate_limit", threshold=100)
assert Constraint.from_dict(c.to_dict()) == c

def test_string_value(self) -> None:
c = Constraint(constraint_type="witness_required", value="true")
def test_hard_default(self) -> None:
c = Constraint(constraint_type="witness_quorum", threshold=3.0)
assert c.hard is True
assert Constraint.from_dict(c.to_dict()) == c

def test_soft_constraint(self) -> None:
c = Constraint(constraint_type="rate_limit", threshold=100, hard=False)
assert c.hard is False
assert Constraint.from_dict(c.to_dict()) == c

def test_float_value(self) -> None:
c = Constraint(constraint_type="atp_minimum", value=50.5)
c = Constraint(constraint_type="atp_minimum", threshold=50.5)
assert Constraint.from_dict(c.to_dict()) == c

def test_legacy_value_key(self) -> None:
"""from_dict accepts legacy 'value' key for backward compatibility."""
d = {"type": "atp_minimum", "value": 42}
c = Constraint.from_dict(d)
assert c.threshold == 42.0
assert c.hard is True


class TestRulesRoundTrip:
def test_empty(self) -> None:
Expand All @@ -48,7 +61,7 @@ def test_full(self) -> None:
law_hash="abc123",
society="test-society",
constraints=[
Constraint("rate_limit", 100),
Constraint("rate_limit", 100.0),
Constraint("atp_minimum", 10.0),
],
permissions=["read", "write"],
Expand Down
10 changes: 2 additions & 8 deletions web4-standard/implementation/sdk/tests/test_role.py
Original file line number Diff line number Diff line change
Expand Up @@ -498,9 +498,7 @@ def test_context_mandatory_excluded(self) -> None:
# ── validate_minimum_viable ───────────────────────────────────


def _make_assignment(
role: SocietyRole, filler: str = "entity-001"
) -> RoleAssignment:
def _make_assignment(role: SocietyRole, filler: str = "entity-001") -> RoleAssignment:
"""Helper to create a minimal RoleAssignment for testing."""
return RoleAssignment(
role=role,
Expand Down Expand Up @@ -528,11 +526,7 @@ def test_empty_roles_fails(self) -> None:

def test_missing_one_base_mandatory(self) -> None:
"""Missing a single base-mandatory role is reported."""
roles = [
_make_assignment(r)
for r in BASE_MANDATORY_ROLES
if r != SocietyRole.TREASURER
]
roles = [_make_assignment(r) for r in BASE_MANDATORY_ROLES if r != SocietyRole.TREASURER]
errors = validate_minimum_viable(roles)
assert len(errors) == 1
assert "treasurer" in errors[0]
Expand Down
4 changes: 3 additions & 1 deletion web4-standard/implementation/sdk/web4/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
- Deserialization — generic JSON-LD dispatcher for all Web4 types
- Generation — produce minimal valid JSON-LD documents for any Web4 type

23 modules + MCP server, 368 exports, 2627 tests, 3 behavioral functions, 8 MCP tools, 7 CLI subcommands.
23 modules + MCP server, 369 exports, 2627 tests, 3 behavioral functions, 8 MCP tools, 7 CLI subcommands.
v0.26.0: CI quality gates (strict mypy, ruff lint, ruff format) enforced on every PR.
These modules define the canonical data types and algorithms specified in the web4-standard.
They work offline (no network services required) and are designed to be
Expand Down Expand Up @@ -397,6 +397,7 @@
SocietyRole,
RoleAssignment,
bootstrap_society_roles,
validate_minimum_viable,
BASE_MANDATORY_ROLES,
)

Expand Down Expand Up @@ -817,6 +818,7 @@
"SocietyRole",
"RoleAssignment",
"bootstrap_society_roles",
"validate_minimum_viable",
"BASE_MANDATORY_ROLES",
# security
"CryptoSuiteId",
Expand Down
49 changes: 37 additions & 12 deletions web4-standard/implementation/sdk/web4/r6.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,19 +124,45 @@ class ReputationComputationError(R7Error):

@dataclass(frozen=True)
class Constraint:
"""A single constraint within Rules."""
"""A single constraint within Rules.

constraint_type: str # e.g. "rate_limit", "atp_minimum", "witness_required"
value: Any # threshold or limit
Cross-language parity with ``web4-core/src/r6.rs::Constraint``.

Attributes:
constraint_type: Constraint kind (e.g. "rate_limit", "min_atp",
"witness_quorum").
threshold: Numeric threshold value for the constraint.
hard: If True the constraint blocks execution; if False it warns
but allows the action to proceed. Defaults to True.
"""

constraint_type: str
threshold: float
hard: bool = True

def to_dict(self) -> Dict[str, Any]:
"""Serialize to dict with 'type' and 'value' keys."""
return {"type": self.constraint_type, "value": self.value}
"""Serialize to dict."""
return {
"type": self.constraint_type,
"threshold": self.threshold,
"hard": self.hard,
}

@classmethod
def from_dict(cls, d: Dict[str, Any]) -> "Constraint":
"""Deserialize from dict produced by to_dict()."""
return cls(constraint_type=d["type"], value=d["value"])
"""Deserialize from dict.

Accepts either ``threshold`` (current) or ``value`` (legacy) key
for the numeric threshold.
"""
raw = d.get("threshold", d.get("value"))
if raw is None:
raise KeyError("Constraint dict must contain 'threshold' or 'value'")
return cls(
constraint_type=d["type"],
threshold=float(raw),
hard=d.get("hard", True),
)


@dataclass
Expand Down Expand Up @@ -165,8 +191,9 @@ def check_constraint(self, constraint_type: str, actual_value: float) -> bool:
"""Check a specific constraint. Returns True if no constraint of that type exists."""
for c in self.constraints:
if c.constraint_type == constraint_type:
if isinstance(c.value, (int, float)):
return actual_value >= c.value if constraint_type.endswith("_minimum") else actual_value <= c.value
return (
actual_value >= c.threshold if constraint_type.endswith("_minimum") else actual_value <= c.threshold
)
return True

def to_dict(self) -> Dict[str, Any]:
Expand Down Expand Up @@ -940,9 +967,7 @@ def from_jsonld(cls, doc: Dict[str, Any]) -> R7Action:
"""
# 1. Rules
rules_data = doc.get("rules", {})
constraints = [
Constraint(constraint_type=c["type"], value=c["value"]) for c in rules_data.get("constraints", [])
]
constraints = [Constraint.from_dict(c) for c in rules_data.get("constraints", [])]
rules = Rules(
law_hash=rules_data.get("lawHash", ""),
society=rules_data.get("society", ""),
Expand Down
10 changes: 2 additions & 8 deletions web4-standard/implementation/sdk/web4/role.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,18 +381,12 @@ def validate_minimum_viable(
# 2. Internal differentiation — at least 2 distinct role-filling entities
unique_fillers = {ra.filling_entity_lct_id for ra in roles}
if len(unique_fillers) < 2:
errors.append(
"Minimum viable society requires at least 2 distinct"
" role-filling entities"
)
errors.append("Minimum viable society requires at least 2 distinct role-filling entities")

# 3. Witnessing capacity — Witness or Auditor role must be assigned
has_witness = SocietyRole.WITNESS in assigned_roles
has_auditor = SocietyRole.AUDITOR in assigned_roles
if not has_witness and not has_auditor:
errors.append(
"Minimum viable society requires witnessing capacity"
" (Witness or Auditor role)"
)
errors.append("Minimum viable society requires witnessing capacity (Witness or Auditor role)")

return errors
9 changes: 7 additions & 2 deletions web4-standard/implementation/sdk/web4/schema_registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -2564,13 +2564,18 @@
"type": "object",
"required": [
"type",
"value"
"threshold"
],
"properties": {
"type": {
"type": "string"
},
"value": {}
"threshold": {
"type": "number"
},
"hard": {
"type": "boolean"
}
},
"additionalProperties": false
},
Expand Down
5 changes: 3 additions & 2 deletions web4-standard/schemas/r7-action-jsonld.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,11 @@
"type": "array",
"items": {
"type": "object",
"required": ["type", "value"],
"required": ["type", "threshold"],
"properties": {
"type": { "type": "string" },
"value": {}
"threshold": { "type": "number" },
"hard": { "type": "boolean" }
},
"additionalProperties": false
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@
"lawHash": "sha256:law_hash_002",
"society": "lct:web4:society:research",
"constraints": [
{"type": "time_limit", "value": 3600},
{"type": "budget_cap", "value": 100}
{"type": "time_limit", "threshold": 3600, "hard": true},
{"type": "budget_cap", "threshold": 100, "hard": true}
],
"permissions": ["read", "analyze"],
"prohibitions": ["delete", "modify"]
Expand Down
Loading