Pilot prep: simplex schema, projection, alignment, lint, stats, pilot scripts, protocol v1
ci / python (pull_request) Successful in 17s
ci / lean (pull_request) Successful in 1m10s
ci / image (pull_request) Has been skipped

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYqwNMyPUHeTbFWg83GQq6
This commit is contained in:
2026-10-22 05:52:28 +00:00
parent 3e0782c62a
commit ca98703538
19 changed files with 2024 additions and 0 deletions
+135
View File
@@ -0,0 +1,135 @@
"""Roles and simplices: the parameters of a declaration as a small typed schema.
A Lean declaration is a bundle of roles: the objects it is about (``(G : Type*)``),
the facts it assumes (``(h : a ∣ b)``), and the goal it asserts or the thing it
defines. A ``Simplex`` is such a bundle with at least two roles. The same schema can
be read off the prose of a book, so the two can be compared role by role.
Design notes (from the archived ``schema-simplex`` prototype):
* a role may be *constrained* to a closed set of allowed values (``choices``);
* a simplex is itself a role, so simplices nest (a lemma used by another is a role
of it), which is how a dependency structure appears;
* validation lives in ``__init__``. The prototype put it in a dataclass
``__post_init__`` next to a hand-written ``__init__``, where it never ran.
"""
from __future__ import annotations
from collections.abc import Sequence
from typing import Any
KIND_OBJECT = "object" # something the statement is about: a group G, an integer n
KIND_FACT = "fact" # something assumed: n > 0, G is finite
KIND_GOAL = "goal" # what is asserted, or asked to be shown
KIND_OWN = "own" # what a definition is
KIND_SIMPLEX = "simplex"
KINDS = (KIND_OBJECT, KIND_FACT, KIND_GOAL, KIND_OWN, KIND_SIMPLEX)
MIN_ROLES = 2
class Role:
"""A named slot with a kind, a description and optionally a closed value set."""
def __init__(
self,
name: str,
kind: str = KIND_OBJECT,
*,
type_text: str = "",
context: str = "",
choices: Sequence[Any] | None = None,
attrs: dict[str, Any] | None = None,
) -> None:
if not name:
raise ValueError("a role needs a name")
if kind not in KINDS:
raise ValueError(f"unknown role kind {kind!r}; expected one of {KINDS}")
self.name = name
self.kind = kind
self.type_text = type_text
self.context = context
self.choices: list[Any] | None = list(choices) if choices is not None else None
self.attrs: dict[str, Any] = dict(attrs or {})
def is_constrained(self) -> bool:
return self.choices is not None
def validate_value(self, value: Any) -> bool:
return self.choices is None or value in self.choices
def check_value(self, value: Any) -> None:
if not self.validate_value(value):
raise ValueError(
f"{value!r} is not allowed for role {self.name!r}; allowed: {self.choices}"
)
def as_dict(self) -> dict[str, Any]:
out: dict[str, Any] = {"name": self.name, "kind": self.kind}
if self.type_text:
out["type"] = self.type_text
if self.context:
out["context"] = self.context
if self.choices is not None:
out["choices"] = self.choices
if self.attrs:
out["attrs"] = self.attrs
return out
def __repr__(self) -> str:
return f"<Role {self.kind}:{self.name}>"
class Simplex(Role):
"""A reusable bundle of at least two roles, keyed by alias. Itself a role."""
def __init__(self, name: str, roles: dict[str, Role], *, context: str = "") -> None:
super().__init__(name, KIND_SIMPLEX, context=context)
if len(roles) < MIN_ROLES:
raise ValueError(
f"simplex {name!r} needs at least {MIN_ROLES} roles, got {len(roles)}"
)
self.roles = dict(roles)
def by_kind(self, kind: str) -> list[Role]:
return [role for role in self.roles.values() if role.kind == kind]
def walk(self) -> list[Role]:
"""Every non-simplex role, descending into nested simplices."""
out: list[Role] = []
for role in self.roles.values():
if isinstance(role, Simplex):
out.extend(role.walk())
else:
out.append(role)
return out
def as_dict(self) -> dict[str, Any]:
return {
"name": self.name,
"roles": {alias: role.as_dict() for alias, role in self.roles.items()},
}
class State:
"""A role together with a value; unresolved until it has one."""
def __init__(self, role: Role, value: Any = None) -> None:
self.role = role
self.value = value
def is_resolved(self) -> bool:
return self.value is not None
class Fact(State):
"""Known at the start."""
def __init__(self, role: Role, value: Any) -> None:
if value is None:
raise ValueError("a Fact must carry a value")
super().__init__(role, value)
class Goal(State):
"""To be resolved."""